Pull to refresh

Comments 19

Очень круто. Это из серии «надо бы сделать такую штуку, но лень». Но бывают и такие тесты:)

Не уверен, что правильно понял идею, но в gcc вроде бы a[5] = {0,0,0,0,0} то же что и a[5] = {0}. Т.е. можно задать длинный-предлинный массив и по ходу регистрации тестов закидывать в него указатели.
Но бывают и такие тесты:)

Да, тут сложнее. Но можно подстраивать максимальный размер под нужды проекта, если надо.

Не уверен, что правильно понял идею, но в gcc вроде бы a[5] = {0,0,0,0,0} то же что и a[5] = {0}.

Это по стандарту, если элементов в массиве больше чем инициализаторов, то остальные будут zero-initialized.

Т.е. можно задать длинный-предлинный массив и по ходу регистрации тестов закидывать в него указатели.

В том то и дело, что нельзя, в этом и сложность. Чтобы присваивать отдельные элементы в настоящем массиве нужно быть внутри функции и делать это во время исполнения, проинициализировать глобально его можно только один раз, дальнейшие попытки вызовут ошибку компиляции.
Оу, авто-регистрация тестов (да и вообще чего угодно) — это извечная тема для C. =)

Я не так давно описывал в комментариях к похожей статье один приём, годный в т.ч. и для регистрации тестов: тут и тут. В итоге получаем возможность писать тесты, например, вот как тут.
Да я эти комментарии видел, спасибо за описание, было интересно почитать о таком варианте.

В соседнем ко второму комментарию есть такая фраза:
Да, средствами чистого Си такую регистрацию тестов сделать не возможно.

Был интересен именно этот вариант. Не в полном объёме, конечно, вышло, но, вроде, более-менее.

Надо упомянуть ваш способ как альтернативный, а то упустил это.
А, чёрт, я как-то на автомате ошибочно решил, что у вас используется GNU'тый диалект!
Приятно, что кто-то довел мою мысль до логического завершения!
Но отмечу, в своей статье я ссылался на вот эту, где тоже используется автоматическая регистрация тестов на чистом С, но с использованием линкера. Насколько я понимаю, вы пошли несколько иным путем, но тоже используете линкер.

Я не уверен, что любой линкер с этим справится.

К вашему подходу у меня пока что есть два довольно глупых вопроса:

1) Если вы вводите ограничение на количество строк в файле, то все тесты после этого ограничения тихо не компилируются, не запускаются и это можно не заметить! Нельзя ли как-нибудь громко падать, если файл с тестами превышает ограничение на размер?

2) Это вопрос скорее эстетический — зачем нужно столько эквивалентных макросов?
assert_true, assert_false, assert_success, assert_failure, assert_null, assert_non_null, assert_int_equal, assert_ulong_equal…
Я пользуюсь только одним — ASSERT(statement, text) и нахожу его совершенно достаточным.
Я не уверен, что любой линкер с этим справится.

Возможно, что и любой, надо добраться до проверки на MSVC. Если и там заработает, то должно много где работать. Ничего особенного здесь нет, эта эксплуатация полностью определённых vs. недо-определённых символов.

1) Если вы вводите ограничение на количество строк в файле, то все тесты после этого ограничения тихо не компилируются, не запускаются и это можно не заметить! Нельзя ли как-нибудь громко падать, если файл с тестами превышает ограничение на размер?

С -Werror=unused-function оно не скомпилируется. Можно макрос с проверкой вставлять, но этого делать и не хотелось. К сожалению, какого-нибудь макроса __LINES__ не дано.

2) Это вопрос скорее эстетический — зачем нужно столько эквивалентных макросов?

Для удобства чтения (меньше парсить в уме и тесты более очевидные) и большей информативности ошибок. success это "== 0", failure это "!= 0". Зачем там ulong не уверен, я только success/failure/null/non_null добавил.
Возможно, что и любой, надо добраться до проверки на MSVC. Если и там заработает, то должно много где работать. Ничего особенного здесь нет, эта эксплуатация полностью определённых vs. недо-определённых символов.

Мне кажется (но, возможно, это инерция мышления), что наиболее востребованная область для тестов на чистом С — это embedded-приложения, где все еще зоопарк древних или просто странных средств разработки, где С++ может быть просто нечем компилировать. Вот в их-то способностях я и сомневаюсь.

В MSVC актуальность чистого С мне представляется не очень большой.
Вот в их-то способностях я и сомневаюсь.

Если способны линковать объектные файлы, то, думаю, и это потянут. Я не вижу ничего нестандартного (кроме немного C99, но это можно убрать), чего может нехватать в тулчейне для embedded, даже если он из начала девяностых. Хотя я могу заблуждаться.

В MSVC актуальность чистого С мне представляется не очень большой.

Согласен, просто обычно, если GCC и MSVC что-то поддерживают одновременно, то это хороший признак повсеместной распространённости так как они из разных миров.
Может, куда-нибудь в тело макроса запихнуть:
if(__LINE__ > 1000) exit(-1);
Ошибка всплывёт не на этапе компиляции, но хотя бы в рантайме.
Спасибо! Конечно, это ведь можно вставить в макрос TEST и сделать там «static assert», оно во время компиляции теперь будет падать.

cast Amomum
Черт, даже обидно, что сам не догадался :)

Немного жаль, что если будет ошибка компиляции, пользователю придется самому вручную исправлять эту константу (одну для всех файлов, насколько я понял). Может, макрос в конце файла — не такое уж большое зло?

Но мой вопрос это снимает.
Насколько я понял, смысл в том, чтобы сообщить разработчику, что пора разбить этот разросшийся файл на несколько частей.
В основном это чтобы не дать пострадать времени компиляции. «Рекурсивное разворачивание» макросов такой большой глубины оказалось действительно медленным, это даёт прирост времени компиляции порядка 0,3 сек. на файл в 1000 строк на i7.

cast Amomum

Может, макрос в конце файла — не такое уж большое зло?

Не зло, конечно, просто мне хотелось избавится от этих вспомогательных элементов. При необходимости можно поддерживать оба подхода ценой увеличения времени компиляции (не факт, что от этого замедления нельзя избавится в принципе).
Совсем недавно были мысли сделать нечто подобное, но все варианты «авторегистрации» с использованием препроцессора не выдержали критики.
Основной аргумент — трудности при отладке. Плюс проблемы с «Go to definition».
Мне показалось это важным, поэтому было принято решение оставить ручную регистрацию.
Если уж зашла речь о том, что нельзя сделать на чистом C.
Как насчет __attribute__((constructor))?
Он позволяет определять функции, которые будет вызваны до main.
Тоже вариант. Кстати, довольно похожий на то, что в Embox. Главное, чтобы компилятор поддерживал и с порядком регистрации разобраться, если тесты на группы делятся, надо отделять «конструкторы» разных типов. Тут, кажется, прийдётся формировать динамические структуры.
Можно добавлять описания всех тестов в один большой массив. Или если жалко виртуальную память, то в аналог std::vector.
А потом разом этот массив регистрировать.
Но да, динамические структуры рано или поздно возникнут, а возможность проставлять приоритет в __attribute__((constructor)) может пригодиться.
Sign up to leave a comment.

Articles