Введение


Данная статья повествует о тестировании и пакетировании программ при помощи CMake — гибкого и универсального набора утилит для разработки различных программных продуктов. Строго рекомендуется прочитать первую и вторую части руководства во избежание непонимания синтаксиса и принципа работы CMake.


Запуск CMake


Ниже приведены примеры использования языка CMake, по которым Вам следует попрактиковаться. Экспериментируйте с исходным кодом, меняя существующие команды и добавляя новые. Чтобы запустить данные примеры, установите CMake с официального сайта.


Пример тестирования


Как было сказано ранее, CMake поддерживает автоматическое тестирование программ. Данную возможность весьма легко использовать — достаточно лишь написать несколько команд в привычном CMakeLists.txt, а затем запустить тесты посредством ctest или make test. В Вашем распоряжении присутствует проверка вывода программ, динамический анализ памяти и многое другое.


Мы рассмотрим процесс тестирования программы на конкретном примере. Исходный файл Multiply.c содержит следующий код:


#include <stdio.h>
#include <stdlib.h>
#define ARG_COUNT 3

int main(const int argc, const char *argv[]) {
    if (argc != ARG_COUNT) {
        fprintf(stderr, "Error!\n");
        return EXIT_FAILURE;
    }

    const int first = atoi(argv[1]);
    const int second = atoi(argv[2]);
    printf("The result is: %d\n", first * second);

    return EXIT_SUCCESS;
}

Данный код выводит результат перемножения двух аргументов в консоль. Обратите внимание, что возможна также ситуация возникновения ошибки, если реальное число аргументов не удоволетворяет ожидаемому: в таком случае в поток ошибок будет выведено Error!.


В том же каталоге находится файл CMakeLists.txt с описанием процесса сборки, содержащий приведённый ниже код:


cmake_minimum_required(VERSION 3.0)
project(MyProgram)
add_executable(Multiply Multiply.c)

set(MULTIPLY_TIMEOUT 1)

# Включить поддержку тестирования:
enable_testing()

# Добавить тесты:
add_test(FirstTest Multiply 15 207)
add_test(SecondTest Multiply -54 -785)
add_test(ThirdTest Multiply 85234)

# Установить поведение тестов:
set_tests_properties(FirstTest SecondTest ThirdTest
PROPERTIES TIMEOUT ${MULTIPLY_TIMEOUT})

set_tests_properties(FirstTest PROPERTIES
PASS_REGULAR_EXPRESSION "The result is: 3105"
FAIL_REGULAR_EXPRESSION "Error!")

set_tests_properties(SecondTest PROPERTIES
PASS_REGULAR_EXPRESSION "The result is: 42390"
FAIL_REGULAR_EXPRESSION "Error!")

set_tests_properties(ThirdTest PROPERTIES
PASS_REGULAR_EXPRESSION "Error!")

Рассмотрим всё по порядку. Первые четыре команды должны быть вам знакомы с предыдущей статьи, а следующая команда enable_testing вызывает ряд вопросов. По существу, данная команда лишь уведомляет CMake о Вашем намерении протестировать программу, попутно генерируя некоторые конфигурационные файлы, о существовании которых Вам знать не обязательно.


Следующие три команды add_test добавляют тесты к текущему проекту. Краткая форма данной команды первым аргументом принимает наименование теста, а последующие аргументы образуют команду оболочки для запуска теста.


Череда команд set_tests_properties устанавливает поведение отдельно взятых тестов. После списка наименований тестов следует ключевое слово PROPERTIES, сигнализирующее о начале списка свойств, имеющих вид <название свойства> <новое значение> и задаваемых для выбранных тестов. Полный список доступных свойств находится тут.


Для всех тестов задаётся максимальное время исполнения в одну секунду свойством TIMEOUT, а затем для последующих тестов устанавливается ожидаемый вывод свойствами PASS_REGULAR_EXPRESSION и FAIL_REGULAR_EXPRESSION (например, если происходит совпадение с регулярным выражением The result is: 3105, то выполнение теста FirstTest продолжается, а в случае совпадения с выражением Error! тест останавливается и считается неудавшимся).


Способы включения тестирования


Существует аналог команды enable_testing — это включение модуля CTest посредством команды include. В общем случае, включение модуля более универсально, однако между ними всё же есть различие.


Команда enable_testing включает тестирование для текущего каталога, а также для всех последующих. Она должна находится в корневом CMakeLists.txt, так как CTest ожидает файл тестирования в корне сборки.


Включение модуля CTest конфигурирует проект для тестирования посредством CTest/CDash, а также автоматически определяет опцию BUILD_TESTING, принимающую истину при возможности проведения тестирования (по умолчанию — ON). Таким образом, при использовании данной команды разумно описывать процесс тестирования подобным образом:


if(BUILD_TESTING)
    add_test(FirstTest Test 1)
    add_test(SecondTest Test 2)
    add_test(ThirdTest Test 3)
    # Следующие команды...
endif()

Исполнение тестирования


Чередой команд cmake . && cmake --build . && ctest . запускаются на выполнение все три теста. Командой ctest -R <RegularExpression> исполняется набор тестов, удоволетворяющих заданному регулярному выражению. Например, команда ctest -R ThirdTest запускает лишь третьй тест.


Пример пакетирования


Для создания пакета исходных файлов, библиотек и исполняемых файлов Вам осталось лишь описать установку необходимых файлов с помощью команды install, а затем включить модуль CPack командой include:


# Установить исполняемый файл "Multiply" в директорию "bin":
install(TARGETS Multiply DESTINATION bin)

# Задать некоторые характеристики пакета:
set(CPACK_PACKAGE_NAME "Multiply")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_CONTACT "https://myprojectsite.org")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "mycontacts@gmail.com")
set(CPACK_PACKAGE_DESCRIPTION "The most stupid program ever written")

# Задать используемый генератор пакетов:
set(CPACK_GENERATOR "DEB")

# Включить стандартный модуль "CPack":
include(CPack)

В данном случае, команда install уведомляет генератора пакетов о каталоге установки цели Multiply. Без написания команд установки генерация пакетов невозможна.


Далее перечисляются характеристики пакета заданием нескольких переменных. На самом деле, существует обилие подобных переменных, должным образом оформляющих пакеты. Большинство из них не обязательны, однако некоторые генераторы пакетов требуют их определения. Список переменных, общих для всех генераторов пакетов, доступен здесь.


Обязательно определение переменной CPACK_GENERATOR — она является списком генераторов пакетов, вызываемых утилитой cpack. В данном случае она принимает значение DEB, следовательно, в корневом каталоге обзорного приложения сгенерируется debian-пакет.


Наконец, подключается модуль CPack, конфигурирующий будущий пакет проекта, используя ранее определённые переменные и команду установки исполняемого файла, а также добавляющий две цели сборки — package и package_source (соответственно бинарная сборка и сборка исходных кодов).


Исполнение пакетирования


Чередой команд cmake . && cmake --build . && cmake --build . --target package запускается на выполнение выбранный генератор для создания бинарного пакета, а команды cmake . && cmake --build . && cmake --build . --target package_source генерируют пакет исходных кодов прямо в корневом каталоге.


Завершение


Статья вышла на свет с опозданием из-за неотложных обстоятельств, но я надеюсь, что Вы её оцените не меньше предыдущих. Разбор исходных файлов CMake популярного проекта выйдет до нового года в виде отдельной статьи (предлагайте варианты в комментариях). Удачи!

Комментарии (3)


  1. ksergey01
    20.12.2018 21:02

    Спасибо, узнал для себя много нового о тестировании с cmake.


    cmake ... && make && ... — так уже не рекомендуется. Лучше cmake ... && cmake --build . && etc


    1. Gymmasssorla Автор
      21.12.2018 07:22

      Согласен, Ваш вариант кроссплатформеннее) Исправлено.


  1. klirichek
    22.12.2018 09:07
    +1

    Ctest это так-то своя независимая тулза, со своими собственными конфиг-файлами (как, в общем, и Cpack). Cmake просто позволяет в своём единственном конфиге CMakeLists.txt настроить конфигурацию и этих вспомогательных тулзов тоже.
    Но в целом, чтобы запустить ctest, не обязательно билдить проект (при этом если в тестах используются цели из проекта, то они, разумеется, не пройдутся).
    Т.е. просто cmake ... && ctest — в некоторых случаях вполне нормально.


    А если воспользоваться тестовым дашбордом, то сборка произойдёт "сама".
    Т.е. cmake ... && ctest -D Experimental, к примеру, сам всё соберёт и протестирует.


    Ну и в конце концов можно пускать ctest вообще без предварительного запуска cmake. Но тогда тестовый скрипт нужно писать самостоятельно (только он уже будет по принципу "хвост виляет собакой" — сам всё конфигурировать, собирать и тестировать). Это вполне норм, например, для автоматизированных CI. Это позволяет делать достаточно гибкие варианты. Например, несколько запусков ctest -S test.cmakeс разными конфигурациями собирают данные, и финальный ctest -S upload.cmake из пары команд ctest_start и ctest_submit одним движением вливают всё накопленное в cdash.