Как вы знаете, стандартная процедура юнит-тестирования с помощью googletest:
- Создание библиотеки, содержащей тестируемый код.
- Напишите свои юнит-тесты googletestв одном или нескольких исходных файлах.
- При желании напишите исходный файл, который определяет
main
в стандартным способом googletest для запуска модульных тестов.Кроме того, вы можете не определять main
, а вместо этого добавлять libgtest_main
к вашей связи. - Скомпилировать все источники googletest из 2 и 3 в объектные файлы.
- Свяжите тест-гугл-тестера, введя все объектные файлы вместе с библиотекой из 1 плюс
libgtest_main
(если вам это нужно), плюс libgtest
Вы хотели бы связать libgtest_main
вместо определения main
, и , вы хотели бы изменять процедуру неортодоксальным способом, архивируя все объектные файлы из 2 в статической библиотеке и добавление ее к вашей связи вместо прямой привязки объектных файлов.
И в вашем опубликованном примере есть еще один тривиальный вариант.У вас нет библиотеки тестируемого кода, потому что она не нужна для иллюстрации вашей проблемы.Вы только что получили модульный тест заполнителя, который обязательно пройдет, если он будет запущен.Проблема в том, что он даже не запускается!
Давайте пока отложим CMake, так как это не имеет значения для объяснения вашей головоломки.Здесь у меня есть ваши файлы, которые были отправлены:
$ ls -R
.:
fixture.cc fixture.hh test.cc
Сначала я собираюсь собрать бегуна googletest по стандартной процедуре (за исключением того факта, что нет тестируемого кода).
Скомпилируйте источники:
$ g++ -Wall -Wextra -c test.cc fixture.cc
Ссылка:
$ g++ -o tester test.o fixture.o -lgtest_main -lgtest -pthread
Запуск:
$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from BarInstantiation/Fixture
[ RUN ] BarInstantiation/Fixture.foo/0
[ OK ] BarInstantiation/Fixture.foo/0 (0 ms)
[----------] 1 test from BarInstantiation/Fixture (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (0 ms total)
[ PASSED ] 1 test.
Ничего плохого в этом нет.
Теперь ясделаю тест-бегун снова так, как вы хотите.Не нужно перекомпилировать какие-либо источники.Я просто создаю статическую библиотеку, включающую fixture.o
:
$ ar rcs libutest.a fixture.o
и перекомпоновываю, используя ее вместо fixture.o
:
$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread
И снова запускаю:
$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[ PASSED ] 0 tests.
Вот твоя загадка.Там нет тестов сейчас.Как получилось?
Наконец, я собираюсь связать тестового бегуна третьим способом:
$ g++ -o tester test.o -lgtest_main -lgtest -pthread
Не связывает либо fixture.o
или libutest.a
.И тот работает:
$ ./tester
Running main() from /home/imk/Downloads/googletest-master/googletest/src/gtest_main.cc
[==========] Running 0 tests from 0 test cases.
[==========] 0 tests from 0 test cases ran. (0 ms total)
[ PASSED ] 0 tests.
точно так же, как второй.В этом случае это не удивительно.Мы знаем , что бегун googletest может быть связан без каких-либо тестов, и это то, что он будет делать.
Давайте немного изучим символы, которые определены в fixture.o
.
$ nm -C --defined-only fixture.o | egrep -w '(W|V)' | wc -l
463
Там 463 слабые символы определены.И определены только 2 сильных символа:
$ nm -C --defined-only fixture.o | egrep -w '(A|B|C|D|G|R|S|T)'
0000000000000000 B Fixture_foo_Test::gtest_registering_dummy_
0000000000000000 T Fixture_foo_Test::TestBody()
Линкер имеет важное значение для различения слабых и сильных символов.
компоновщик не будет связывать программу, которая содержит какую-либо сильную символьную ссылку, которую он не может разрешить, т. е. для которой он не может найти определения в связи.Не удастся связать с ошибками undefined reference .Но он не должен разрешать слабые ссылки для ссылки на программу.Неразрешенная ссылка на слабый символ просто остается нулевой (= 0) в программе.
Компоновщик не будет связывать программу (или совместно используемую библиотеку), чья связь создает несколько строгих определений одного и того же символа.Он не сможет установить связь с ошибками множественное определение .Но он будет допускать несколько слабых определений, а также одно строгое определение одного и того же символа, и в этом случае он выбирает строгое определение для разрешения ссылок и отбрасывает все слабые.И он будет терпеть несколько слабых определений символа без какого-либо сильного.В этом случае он выберет одно из слабых определений произвольно - на практике, только первое, которое видит - и отбросит все остальное.
Компилятор C ++ обычно генерирует смеси определений слабых и сильных символов в порядкеперевести C ++ в успешно скомпонованный объектный код.Встроенные методы классаи экземпляры шаблонов функций должны быть скомпилированы со слабыми определениями.
Различные обязательства компоновщика перед слабыми ссылками и сильными ссылками связаны с его различными обязательствами перед входным файлом, который является объектным файлом икоторая представляет собой статическую библиотеку .
Когда вы предлагаете объектному файлу foo.o
компоновщику, он безоговорочно свяжет его с программой .
A статическая библиотека - это архив объектных файлов, которые вы предлагаете компоновщику , из которого можно извлечь только те, которые ему нужны , и связать их впрограмма.По умолчанию он не будет извлекать и связывать любой из них безоговорочно.Только те, которые разрешают символы, уже упомянутые в связи , для которых нужно найти определения .
Но ссылка на слабый является той, которую компоновщик не нужно разрешать , чтобы связать программу.Таким образом, по умолчанию никогда не извлекает и не связывает объектный файл из статической библиотеки, чтобы разрешить слабую ссылку на символ.Ссылка может быть неопределенной в программе, поэтому компоновщик не будет тратить усилия на поиск библиотек для ее определения.
Собирая факты, которые мы только что просмотрели, мы знаем, что связь:
$ g++ -o tester test.o fixture.o -lgtest_main -lgtest -pthread
, который выполнил TEST_P
, определенный в fixture.cc
, - это тот, который вставит ссылку fixture.o
в программу, потому что это объектный файл.
И мыТакже известно, что связь:
$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread
, которая выполнила 0 тестов, извлечет и свяжет libutest.a(fixture.o)
с программой только , если test.o
содержит хотя бы одну ссылку на сильный символэто определено в fixture.o
, то есть ссылка на один из:
Fixture_foo_Test::gtest_registering_dummy_
Fixture_foo_Test::TestBody()
, поскольку все 463 других символа, определенных в fixture.o
, являются слабыми.
Но test.o
не делает никакихссылки на любые символы, содержащие Fixture_foo_Test
:
$ nm -C test.o | grep Fixture_foo_Test; echo Done
Done
Таким образом, мы можем заключить, что связь:
$ g++ -o tester test.o -L. -lutest -lgtest_main -lgtest -pthread
будет не извлекать и ссылаться libutest.a(fixture.o)
.Это точно такая же связь, как:
$ g++ -o tester test.o -lgtest_main -lgtest -pthread
, которая, как мы знаем, содержит 0 тестов.
Помещение fixture.o
в статическую библиотеку делает его ненужным длясвязь tester
, и она не связана.
В этом свете ваш файл CMakeLists.txt легко исправить, упростив его до:
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
enable_testing()
add_subdirectory(googletest)
include(GoogleTest)
add_executable(unit_tests test.cc fixture.cc)
target_link_libraries(unit_tests PRIVATE gtest_main gtest)
gtest_discover_tests(unit_tests)
, чтов результате fixture.o
будет безоговорочно связан с unit_tests
, и программа выдаст:
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from BarInstantiation/Fixture
[ RUN ] BarInstantiation/Fixture.foo/0
[ OK ] BarInstantiation/Fixture.foo/0 (0 ms)
[----------] 1 test from BarInstantiation/Fixture (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
, как вы и надеялись.
Очевидно, что это решение не удовлетворяет вашей заявленной мотивации для создания libtest_utils.a
в первую очередь:
Моя цель состоит в том, чтобы просто создать библиотеку с классом текстовых фикстур и набором тестов TEST_P, с которыми могут быть связаны отдельные исполняемые модули модульных тестов, чтобы минимизировать дублирование кода.
Но эта цель неверна.Нет смысла создавать статическую библиотеку из набора TEST_P
тестовых источников (или любых источников), если вы собираетесь перестроить ее из тех же источников для связи с каждымисполняемый файл, которому нужны некоторые объектные файлы, которые вы будете архивировать в статической библиотеке.Если вы собираетесь перестраивать его из исходного кода для каждого исполняемого файла, который в этом нуждается, вы минимизируете дублирование кода, просто сохраняя все TEST_P
тестовые источники в одном общем месте или хранилище, и все еще не будетТочка в перестройке статической библиотеки для каждого исполняемого файла.Каждый из этих исполняемых проектов может также включать некоторые или все TEST_P
источники библиотеки из своих собственных источников и не беспокоить перестройку библиотеки или создание ссылок на нее.
С другой стороныс другой стороны, у вас будет мотивация повторного использования кода для создания статической библиотеки из TEST_P
источников тестирования, если вы намеревались , а не , чтобы перестраивать ее для каждогоИсполняемый проект модульного теста, но чтобы построить его как независимый проект и просто использовать его в связке исполняемых файлов модульного теста, которую вы создадите позже.Однако вы не хотите делать , что , по тем же причинам, по которым сам googletest лучше всего встроен в каждый проект модульного тестирования, использующий его , так же, как и вы.Он отрицает цель создания googletest как зависимости в проекте, если затем связать исполняемый файл с библиотекой объектных файлов, которые были скомпилированы когда-то в прошлом, в другом проекте с возможно другой ревизией googletest, возможно, с другим компилятором.
Вы хотите, чтобы googletest создавался вашим проектом модульного тестирования, а ваши TEST_P
исходники компилировались и связывались с тем же googletest в том же проекте.Поэтому нет смысла архивировать объектные файлы TEST_P
, которые вы встраиваете в промежуточную статическую библиотеку, в отличие от простого связывания их непосредственно в исполняемом модульном тесте.И, если вы do поместите их в промежуточную статическую библиотеку, по умолчанию компоновщик будет их игнорировать.
Я сказал по умолчанию , компоновщик будет их игнорировать, потому чтоможно заставить компоновщик связать объектный файл из статической библиотеки, даже если это не нужно.Вы можете сделать это, заставив его извлечь и связать все объектные файлы в статической библиотеке, используя параметр --whole-archive
компоновщика .Если вызывать компоновщик как обычно через gcc / g ++ (или clang / clang ++), параметры связывания, которые вам понадобятся для статической библиотеки libbar.a
, будут следующими:
... -Wl,--whole-archive -lbar -Wl,--no-whole-archive ...
Вам необходимо включить--whole-archive
до -lbar
и отключите его впоследствии, потому что в противном случае он будет применяться не только к -lbar
, но и ко всем последующим библиотекам, включая даже те, которые вы даже не упоминаете в командной строке и которые связаны по умолчанию.
Если вы принудительно связываете все объектные файлы в libbar.a
, то, конечно, любые слабые определения, которые они содержат , будут использоваться для разрешения слабых ссылок на символы в программе.
Тем не менее, не так-то просто получить опции связывания -Wl,--whole-archive -lbar -Wl,--no-whole-archive
в проекте CMake, который строит libbar.a
.См. этот вопрос и ответ.И опять же, в этом нет никакого смысла, так зачем бороться с рамками?Googletest ожидает, что вы создадите свои юнит-тесты в своем проекте юнит-тестов и сделаете очевидную вещь: свяжите объектные файлы с программой.Если вы сделаете это, ваши юнит-тесты будут связаны и запущены, как вы ожидаете.