Это заняло некоторое время, но я понял это. Хитрость в том, что статический инициализатор функции будет работать, например,
int someFunc() {
return 42;
}
int someVal= someFunc();
, если они не вызывают конструкторы, не используют new / malloc и не используют printf. (Мне потребовалось некоторое время, чтобы понять, что Gunslinger47 был прав насчет того, что printfs все испортил.)
Тот факт, что статические функции инициализатора работают, достаточно, чтобы заставить работать UnitTest ++. То, что мы делаем, - это использование варианта обхода «Указатели», описанного здесь :
- Вместо статического распределения каждый тестовый класс имеет функцию распределителя
- Указатель на каждую функцию-распределитель добавляется в список указателей на функции
- В основном, этот список указателей функций затем повторяется, и каждая функция вызывается.
Ниже приведены мелкие детали:
(1) В TestMacros.h измените макрос TEST_EX, чтобы использовать статическую функцию инициализатора, а не конструктор:
#define TEST_EX(Name, List) \
class Test##Name : public UnitTest::Test \
{ \
public: \
Test##Name() : Test(#Name, UnitTestSuite::GetSuiteName(), __FILE__, __LINE__) {} \
private: \
virtual void RunImpl() const; \
}; \
\
void create_test##Name##Instance() { \
Test##Name *test##Name##Instance= new Test##Name(); \
UnitTest::ListAdder adder##Name (List(), test##Name##Instance); \
} \
\
UnitTest::test_creator_func_t fp_create_test##Name##Instance= \
UnitTest::addTestCreator(create_test##Name##Instance); \
\
void Test##Name::RunImpl() const
#define TEST(Name) TEST_EX(Name, UnitTest::Test::GetTestList)
(2) Измените TEST_FIXTURE_EX аналогично TEST_EX. Я избавлю вас от многословия.
(3) В нижней части TestList.cpp добавьте функции, которые вызывают макросы TEST_EX / TEST_FIXTURE_EX:
#if !defined(MAX_TEST_CREATORS)
#define MAX_TEST_CREATORS 1024
#endif
const size_t max_test_creators= MAX_TEST_CREATORS;
size_t num_test_creators= 0;
// This list unfortunately must be static-- if we were to
// dynamically allocate it, then alchemy would break.
// If it winds up not being big enough, then just inject
// a bigger definition for MAX_TEST_CREATORS
test_creator_func_t test_creator_list[max_test_creators]= {NULL};
test_creator_func_t addTestCreator(test_creator_func_t fp) {
int idx= num_test_creators;
num_test_creators++;
if (num_test_creators > max_test_creators) {
throw "test creator overflow";
}
test_creator_list[idx]= fp;
return fp;
}
void initializeAllTests() {
for (size_t idx= 0; idx < num_test_creators; idx++) {
test_creator_list[idx]();
}
}
и, конечно, добавить свои прототипы в TestList.h:
typedef void (*test_creator_func_t)();
test_creator_func_t addTestCreator(test_creator_func_t fp);
void initializeAllTests();
(4) Наконец, в вашем модуле тестирования модулей вы должны вызвать initializeAllTests:
UnitTest::initializeAllTests();
return UnitTest::RunAllTests();
Но это еще не все! Есть еще несколько лакомых кусочков, которые нужно сделать, прежде чем это сработает:
(1) Убедитесь, что UNITTEST_USE_CUSTOM_STREAMS определен в Config.h:
// by default, MemoryOutStream is implemented in terms of std::ostringstream, which can be expensive.
// uncomment this line to use the custom MemoryOutStream (no deps on std::ostringstream).
#define UNITTEST_USE_CUSTOM_STREAMS
Причина этого заключается в том, что если он не определен, MemoryOutStream.h выдаст #include <sstream>
, что нарушит статическую инициализацию (я подозреваю, что он выполняет какой-то глобальный конструктор или что-то в этом роде).
(2) В SignalTranslator.h убедитесь, что макрос UNITTEST_THROW_SIGNALS является noop. Я делаю это, вставляя -D__ALCHEMY__
в мои сборки и проверяя это:
#if defined(__ALCHEMY__)
#define UNITTEST_THROW_SIGNALS
#else
#define UNITTEST_THROW_SIGNALS \
UnitTest::SignalTranslator sig; \
if (UNITTEST_EXTENSION sigsetjmp(*UnitTest::SignalTranslator::s_jumpTarget, 1) != 0) \
throw ("Unhandled system exception");
#endif
Если этого не сделать, вызов sigsetjmp не будет выполнен во время выполнения.