USER
Привет, ты экспертный технический писатель. Несколько разработчиков дали тебе описание различных концепций как работает система сборки bob для проведения тестирования, команда bob test. Это аналог команды сборки bob make -A или bob make -ttt. Вот тексты от разработчиков "Запуск тестов
bob make предоставляет развитые возможности запуска тестов. Основными понятиями для тестирование силами bob make являются test, chunk и suite (набор тестов).
Test - одна именованная проверка, описанная в виде кода на поддерживаемом языке программирования. В понятие теста включается не только проверка правильности работы кода, но также, например, проверки, что код соответствует рекомендуемому стандарту оформления. Любые падения/ошибки во время исполнения теста относятся непосредственно к исполняемому тесту.
Chunk - сущность, представляющая собой запуск программы с тестами (представлен в виде узла в графе команд), в рамках которой исполняются тесты. К chunk относятся все ошибки runtime исполнения за рамками тестов. Например, ошибки запуска тестирования, когда тестовая программа упала на инициализации и тесты даже не запускались, ошибки финализации тестирования, утечки памяти после тестирования, падения тестовых рецептов.
Suite - набор из нескольких тестов одного типа и имеющих один и тот же набор зависимостей. Suite содежит по крайней мере один chunk (так называемый sole chunk). Все тесты в наборе имеют общий размер, логику начала (set up) и завершения (tear down) работы, теги и так далее. Некоторые ошибки не позволяют вообще запустить тестирование - сломанные зависимости теста, когда невозможно собрать требуемую для тестирования программу, ошибки подбора хоста для LARGE тестов из-за комбинации sandbox-тегов, которым не соответствует ни один хост. Все такие ошибки относятся к suite.
Каждая suite имеет имя, размер (время, отведённое на работу), тип (используемый фреймворк), а также опциональный набор тегов. Все эти параметры могут быть использованы для фильтрации. Кроме того, каждый отдельный тест имеет имя и потому фильтрация может делаться с точностью до отдельного теста.
Примечание
Имена suite известны системе сборки заранее, а имена тестов выясняются только во время запуска chunk. Это, в частности, означает, что система сборки должна запустить chunk даже если ей надо только узнать имена тестов. Если в коде инициализации chunk есть ошибки, то такой запуск может завершиться ошибкой и система сборки не узнает, какие тесты есть в suite и даже сколько их там.
Как описывать тесты, можно прочитать в соответствующем разделе руководства по системе сборки bob make.
Локальная работа с тестами
Полный перечень опций тестирования и их описание можно найти здесь.
По умолчанию локально результаты тестов не кэширутся.
На данный момент локальный запуск тестов учитывает таймауты в соответствии с размером (1 минута для SMALL, 10 для MEDIUM, час для LARGE), но не ограничивает ресурсы в соответствии с REQUIREMENTS.
По умолчанию, локальная сборка производится в конфигурации debug, а в автосборке relwithdebinfo (релизная с ассёртами), поэтому поведение и производительность тестов может отличаться.
Результаты тестов локально складываются в директорию test-results, которая появляется символьной ссылкой в рабочей копии в директории теста (кроме Windows или при использовании флага --no-src-links).
Внутри находится директория <suittype>/testing_out_stuff в которой находятся логи тестов и прочие файлы, которые тесты порождают.
Если тесты не прошли успешно, в консоль может быть выведена информация о путях в реальных директориях внутри сборочного каталога, где выполнялись тесты. Чтобы сохранить эти данные после завершения тестов, сборочные директории не очищаются сразу, а только при следующем запуске сборки. Таким образом, данные в этих каталогах доступны до следующей сборки или запуска тестов. Если вам необходимо сохранить эти данные, скопируйте их заранее.
Запуск тестов (-t, -tt, -ttt, -A)
-t: Эта базовая опция запускает все тесты, отмеченные как “маленькие” (SMALL). Это быстрые тесты, обычно требующие мало ресурсов и времени для выполнения.
-tt: Расширение базовой опции -t, которое включает в себя запуск тестов как “маленьких”, так и “средних” (MEDIUM) размеров. Средние тесты обычно занимают больше времени и ресурсов.
-ttt: Данная опция запускает тесты всех размеров, включая “большие” (LARGE). Большие тесты часто включают в себя интеграционные и нагрузочные тесты, требующие значительного времени для выполнения и могут включать внешние зависимости.
-A, --run-all-tests: Аналогично -ttt, запускает все тесты независимо от размера.
Управление выводом результатов
-L, --list-tests: Выводит список тестов, которые будут выполнены, без их фактического запуска. Помогает разработчикам быстро проверить, какие тесты включены в план тестирования.
–fail-fast: Прекращает выполнение тестового прогона сразу после первой встреченной неудачи. Эта опция полезна для экономии времени и ресурсов, особенно когда разработчики ищут конкретную ошибку.
Выборочное тестирование
–test-filter=TESTS_FILTERS: Эта опция позволяет ограничить тестирование только определенными тестами, соответствующими указанным фильтрам. Это может быть имя теста, его часть или другой идентифицирующий шаблон.
–test-tag=TEST_TAGS_FILTER: Позволяет запускать только те тесты, которые помечены определенными тегами. Теги — это пользовательские метки, которые могут быть нанесены на тесты для их группировки по определенным признакам или функциональности.
–test-size=TEST_SIZE_FILTERS: Фильтр для запуска тестов определенного размера (SMALL, MEDIUM, LARGE), позволяющий более точно настроить объем запускаемых тестов в зависимости от текущих потребностей.
–test-type=TEST_TYPE_FILTERS: Ограничивает запуск только теми тестами, которые относятся к указанным типам (например, UNITTEST, PYTEST). Удобно при необходимости проведения специфичных видов тестирования.
Запустит все тесты, которые найдёт по RECURSE/RECURSE_FOR_TESTS от devtools/examples/tutorials/python, включая тесты стиля и тесты импорта для Python. Использует следующие умолчания для сборки:
Платформа будет определена по реальной платформе, на которой запущена команда bob make.
Тесты будут собраны в режиме debug — он используется по умолчанию.
Кроме тестов будут собраны все остальные цели (библиотеки и программы), достижимые по RECURSE/RECURSE_FOR_TESTS от devtools/examples/tutorials/python. Это включает сборку всех необходимых зависимостей.
По умолчанию система сборки запустит все запрошенные тесты. После запуска тестов для всех упавших тестов будет выдана краткая информация о падениях (включая ссылки на более полную информацию). Для прошедших и проигнорированных (отфильтрованных) тестов будет выдан только общий короткий статус (количество тех и других).
Это поведение меняется следующими ключами:
--fail-fast — исполнять тесты до первого падения.
-P, --show-passed-tests — показывать каждый прошедший тест
--show-skipped-tests — показывать каждый пропущенный (отфильтрованный) тест
--show-metrics — показывать метрики тестов
Размер тестов должен быть явно указан в файле bob.make при помощи макроса SIZE. Максимальное время выполнения теста можно уменьшить при помощи макроса TIMEOUT.
Параллельный запуск тестов
По умолчанию тесты внутри одной suite выполняются последовательно в рамках одного chunk (так называемого sole chunk) в виде отдельного узла графа команд bob. Тесты из разных suite (bob.make) выполняются параллельно, но последовательно в рамках одно chunk.
Во время формирования графа команда bob не может знать, сколько будет тестов в suite, так как для этого требуется сборка и листинг тестов средствами тестового фреймворка. Так как bob оперирует статическим графом команд (он не меняется по мере исполнения и целиком известен для исполнителя), то на этапе конфигурации мы только можем заранее вставить нужное количество chunk'ов, каждый из которых исполнит непересекающееся множество тестов.
Чтобы разбить выполнение тестов из suite на несколько chunk'ов, нужно воспользоваться макросами FORK_TESTS, FORK_SUBTESTS и FORK_TEST_FILES.
Каждый chunk наследует общие параметры теста из bob.make: размер, таймаут, требования на ресурсы и другие: таким образом удобно распараллелить тесты, которые из-за своего количества перестали укладываться в таймаут.
FORK_TESTS, FORK_SUBTESTS – режимы, при которых вместо sole chunk запуск suite разбивается на несколько chunk'ов, каждый из которых получает дополнительные параметры в виде общего количества chunk'ов, участвующих в разбиении и своего порядкового номера: по этим параметрам каждый chunk определяет подмножество тестов, которое надо запустить. Количество chunk'ов по умолчанию равно 10, это значение можно изменить с помощью SPLIT_FACTOR(X) (при локальной разработке SPLIT_FACTOR можно переопределить с помощью --split-factor=Х. --split-factor=1 отключает режим форков). Отличие FORK_TESTS от FORK_SUBTESTS заключается в том, что FORK_TESTS считает тесты, объединенные в классы, неделимой сущностью, в то время как FORK_SUBTESTS разбивает в том числе тестовые классы и в разных chunk'ах могут оказаться тесты, принадлежащие одному классу, что может быть нежелательно, если у классов есть тяжёлые подготовительные стадии.
FORK_TEST_FILES – разбивает прогон тестов из suite на количество chunk'ов, равное количеству файлов с тестами, перечисленных в bob.make, каждый запуск работает только с одним конкретным файлом.
FORK_TESTS, FORK_SUBTESTS принимают необязательный аргумент - SEQUENTIAL или MODULO, определяющий, как будет происходить разделение тестов по chunk'ам. SEQUENTIAL разделяет тесты равными диапазонами, предварительно их отсортировав по имени. MODULO разделяет тесты по модулю SPLIT_FACTOR, предварительно их отсортировав (т.е. при SPLIT_FACTOR(2), первый тест попадёт в первый chunk, второй тест во второй, третий в первый и т.д.) Если аргумент не указан, значение по умолчанию - SEQUENTIAL.
FORK_TEST_FILES можно комбинировать с FORK_TESTS или FORK_SUBTESTS, тогда будет дополнительное разбиение запуска тестов из каждого файла.
FORK_TEST_FILES поддержан только для pytest.
Фильтрация тестов
Suite можно фильтровать по различным свойствам:
--test-size=TEST_SIZE_FILTERS — запускать только выбранные размеры
--test-type=TEST_TYPE_FILTERS — запускать только выбранные типы тестов. Перед именами типов тестов можно использовать + для включения suite в фильтр и - для исключения. Таким образом --test-type -import_test запустит все тесты, кроме import_test. Фильтр --test-type unittest+gtest запустит только unittest и gtest.
--style — запускать только тесты стиля
--regular-tests — запускать только пользовательские тесты
--test-tag=TEST_TAGS_FILTER — запускать только сюиты с определёнными тегами. Перед именами тегов можно использовать + для включения тега в фильтр и - для исключения. Таким образом --test-tag tag1+tag2-tag3 запустит все тесты, у которых есть tag1, tag2 и нет tag3. Фильтр --test-tag -tag3 запустит все тесты без тега tag3. Фильтр --test-tag bob:anytag запустит все тесты со всеми тегами.
Помимо фильтрации suite можно фильтровать отдельные тесты:
-F=TESTS_FILTERS, --test-filter=TESTS_FILTERS — фильтрация тестов по имени. Будет запущен тест, полное имя которого строго соответствует TESTS_FILTERS. Для запуска подмножества тестов в шаблоне можно указать символ * (соответствует любому количеству символов). Каждый последующий шаблон расширяет подмножество запускаемых тестов. Например -F '*a' -F 'B*c' запустит все тесты имена которых заканчиваются на a или начинаются на B и заканчиваются на c. Шаблоны взяты в одинарные кавычки для того чтобы shell не развернул фильтр в список файлов, которому может соответствовать шаблон.
-F="[X/Y] chunk", --test-filter="[X/Y] chunk" — фильтрация тестов по chunk'ам. Внутри [] можно использовать * для фильтрации.
--test-filename=TEST_FILES_FILTER — запускать тесты только из выбранного исходного файла (работает только для pytest и hermione, другие тестовые фреймоврки не предоставляют такой информации).
-X, --last-failed-tests — запустить только тесты упавшие в предыдущем запуске. Указание дополнительных фильтров (например с помощью -F) расширяет множество тестов, которое будет запущено. Обычно это требуется, когда вы хотите следить за корректным статусом некоторых тестов, помимо перезапуска упавших от предыдущего прогона.
При указании флага -F можно использовать специальные символы:
bob make -t -F <file>.py::<ClassName>::*
JUnit5-тесты можно фильтровать по тегам @Tag:
--junit-args '--junit-tags "tag1 tag2 tag3"' - запуск всех тестов, у которых есть хотя бы один из указанных тегов (теги можно также разделять плюсом, например tag1+tag2). Подробнее можно почитать тут.
Совет
Для правильного указания параметров фильтрации воспользуйтесь опцией получения списка тестов. Её же можно использовать, чтобы проверить, что ваш фильтр работает правильно.
Канонизация (и переканонизация)
Система сборки bob make для некоторых типов тестов поддерживает сравнение с эталонными (каноническими) данными. Если эти данные нужно обновить воспользуйтесь опцией -Z (--canonize-tests). В этом режиме вместо сравнения данных с эталонными, сами эталонные данные будут заменены и при необходимости отправлены в Sandbox. В локальное рабочее пространство будут внесены все необходимые изменения.
Описание тестов
В этом разделе подробно рассказано как описывать тесты в bob.make файлах. Про запуск тестов можно прочитать вот здесь
В нашей системе сборки bob make можно описать тесты для основных языков в аркадии. Поддержка тестов реализована поверх фреймворков для тестирования в этих языках. Подробную информацию про устройство тестов в языке можете найти на соответствующих страницах (C++, Python, Java, Go).
Общие понятия
Два основных понятия для тестов в bob make это test и suite.
test - это конкретная именованная проверка.
suite - сущность, включающая в себя тесты в рамках описываемого модуля.
Как описывать bob.make
По умолчанию один тестовый модуль является одной suite. suite аккумулирует в себе ошибки тестирования, которые выходят за пределы определения теста, например:
ошибки получения списка тестов
ошибки инициализации тестирования (до фактического выполнения тестов)
ошибки финализации тестирования
Сьюита имеет несколько параметров:
Указание фрэймворка тестирования
Список файлов с тестами
Список зависимостей
Размер
Тэги
Требования к запуску тестов
Переменные окружения
Таймаут на запуск
Список зависимостей
Мы придерживаемся идеи герметичности тестов. Это значит, что тест должен быть зависимым только от входных данных, которые были явно задекларированы в bob.make. Чтобы обеспечить герметичность тестов, каждый запуск проходит в чистом окружении, которое содержит только указанные явно зависимости из bob.make.
Помимо сборочных зависимостей (описываются макросом PEERDIR()), для тестов нужно описывать зависимости на входные данные для запуска тестов. Они бывают двух типов:
Другие проекты из единого репозитория. Например, вам может потребоваться исполняемый файл, исходные коды которого расположены в другом проекте. Такие зависимости описываются при помощи макроса DEPENDS(). Все пути строятся относительно корня PROJECT и указываются через пробел до bob.make.
Тестовые данные. Например, сюда относятся различные эталонные файлы: логи, списки, тестовые дампы баз данных. Такие файлы могут храниться как и в PROJECT, так и в Sandbox. Для описания тестовых данных можно использовать макросы:
DATA()
FROM_SANDBOX()
Более полную информацию про использование данных в тестах можно прочитать здесь.
Сборка и запуск тестов
Основная задача bob make - cборка. Поэтому handler собирает все указанные цели и достижимые от них по RECURSE.
Однако, основная цель у bob test / bob make -t - запуск тестирования. Поэтому по умолчанию будут собираться только те цели, которые необходимы для запуска тестирования. Это, например, позволяет запускать конкретные типы тестов без сборки: bob test --test-type black запустит только python black линтер, без какой-либо сборки.
Примечание
Модули, достижимые по RECURSE, но не используемые в тестах или сами не являющиеся тестовыми модулями не будут собираться.
Для того чтобы bob test / bob make -t собирал все достижимые цели, нужно добавить ключ -b / --build-all.
Как описывать bob.make
По умолчанию один тестовый модуль является одной suite. suite аккумулирует в себе ошибки тестирования, которые выходят за пределы определения теста, например:
ошибки получения списка тестов
ошибки инициализации тестирования (до фактического выполнения тестов)
ошибки финализации тестирования
Сьюита имеет несколько параметров:
Указание фрэймворка тестирования
Список файлов с тестами
Список зависимостей
Размер
Тэги
Требования к запуску тестов
Переменные окружения
Таймаут на запуск
Список зависимостей
Мы придерживаемся идеи герметичности тестов. Это значит, что тест должен быть зависимым только от входных данных, которые были явно задекларированы в bob.make. Чтобы обеспечить герметичность тестов, каждый запуск проходит в чистом окружении, которое содержит только указанные явно зависимости из bob.make.
Помимо сборочных зависимостей (описываются макросом PEERDIR()), для тестов нужно описывать зависимости на входные данные для запуска тестов. Они бывают двух типов:
Другие проекты из единого репозитория. Например, вам может потребоваться исполняемый файл, исходные коды которого расположены в другом проекте. Такие зависимости описываются при помощи макроса DEPENDS(). Все пути строятся относительно корня PROJECT и указываются через пробел до bob.make.
Тестовые данные. Например, сюда относятся различные эталонные файлы: логи, списки, тестовые дампы баз данных. Такие файлы могут храниться как и в PROJECT, так и в Sandbox. Для описания тестовых данных можно использовать макросы:
DATA()
FROM_SANDBOX()
Более полную информацию про использование данных в тестах можно прочитать здесь.
Сборка и запуск тестов
Основная задача bob make - cборка. Поэтому handler собирает все указанные цели и достижимые от них по RECURSE.
Однако, основная цель у bob test / bob make -t - запуск тестирования. Поэтому по умолчанию будут собираться только те цели, которые необходимы для запуска тестирования. Это, например, позволяет запускать конкретные типы тестов без сборки: bob test --test-type black запустит только python black линтер, без какой-либо сборки.
Примечание
Модули, достижимые по RECURSE, но не используемые в тестах или сами не являющиеся тестовыми модулями не будут собираться.
Для того чтобы bob test / bob make -t собирал все достижимые цели, нужно добавить ключ -b / --build-all.
Размер тестов
Размер тестов задается макросом SIZE(). Сейчас существуют три размера:
SIZE(SMALL) - максимальный таймаут 60s (1min). Размер по умолчанию.
SIZE(MEDIUM) - максимальный таймаут 600s (10min).
SIZE(LARGE) - максимальный таймаут 3600s (1hour).
Тэги теста
Тесты можно фильтровать по тэгам. Тэги проставляются с помощью макроса TAG().
Требования к запуску теста
Макрос REQUIREMENTS() позволяет указать требования, необходимые для запуска тестов. С помощью этого макроса можно задать:
количество ядер
необходимый объем свободного места на диске
необходимый объем оперативной памяти
необходимый объем RAM-диска
ID контейнера
ограничения сети
Все возможные описания требований указаны в разделе Общие макросы
Переменные окружения
С помощью макроса ENV(key=value) можно задать значение переменной окружения при запуске теста. Каждая переменная окружения задается отдельным макросомм.
Иерархия проекта с тестами
Предлагается в папке проекта иметь lib, bin, tests или lib/tests. Мы не рекомендуем указывать RECURSE в bob.make, содержащем описание модуля, но можно после директивы END() добавлять RECURSE_FOR_TESTS на тесты, проверяющие этот модуль. Тогда project/bob.make может состоять из
RECURSE(
project/bin
project/lib
project/lib/tests
)
В project/lib не должно быть RECURSE на tests или определение модуля тестов, однако можно поставить RECURSE_FOR_TESTS.
В этом случае при запуске bob make -t из каталога project инициируется сборка проекта с прогоном тестов. А другие проекты, желая подключать вашу библиотеку, будут писать PEERDIR(project/lib) без установления зависимости от ваших тестов.
Гранулярные тесты
Гранулярные зависимости в сборке это хорошо, тоже самое относится и к тестам. Если у вас в тестовом модуле более ~15 файлов с тестами (к java не относится) скорее всего вам нужно разбить ваш suite на несколько логических. Как правило всё множество тестов можно кластеризовать по каким-либо признакам - по тестируемым сущностям, по зависимостям, по типу тестирования, по сценариям тестирования.
Такое разбиение больших тестовых suite может значительно ускорить локальную разработку и время прохождения PR в CI:
при исправлении тестов или добавлении новых будет запускаться только целевой suite
более гранулярные зависимости позволяют запускать только те тесты, которые по настоящему зависят от них
более гранулярные зависимости ускоряют конфигурацию
ускоряется рантайм тестирования, так как ускоряется процедура дискаверинга тестов
в каждый отдельный suite может быть прописан REQUIREMENTS соответствующий действительности
логическая группировка упрощает организацию тестовых файлов
Пример антипаттерна: тестовый модуль с сотнями тестовых файлов и макросами FORK_TEST()/FORK_TEST_FILES(). Такая организация suite приводит к огромному прожиганию ресурсов впустую, так как при изменении любого файла будут вынуждены перезапуститься абсолютно все тесты в suite. Локальный запуск одного конкретного test case из такого suite может происходить очень долго.
Возможности тестов
Канонизация
У тестов есть возможность сообщить о своих результатах в виде данных, которые необходимо верифицировать с каноническими. Такие тесты необходимо первый раз канонизировать, после чего системе будут доступны референсные данные для сравнения. Канонический результат будет сохранен рядом с тестом в директорию canondata/<test name>/result.json или вынесен в ресурс Sandbox, в зависимости от переданных параметров в тесте.
Каждый фреймворк для написания тестов предоставляет свою поддержку механизма канонизации. Канонизация поддержана для всех языков, кроме C++.
Метрики
Наш CI поддерживает выгрузку пользовательских метрик из тестов. При подключении проекта к автосборке история теста с метриками будет сохраняться и отображаться в CI. При локальной сборке будет доступен отчет.
Параллельный запуск тестов
По умолчанию тесты внутри одного bob.make файла выполняются последовательно в рамках отдельной задачи сборочного графа, а тесты из разных bob.make выполняются параллельно. Для того, чтобы разбить выполнение тестов из одного bob.make на несколько параллельных запусков, можно воспользоваться макросами FORK_TESTS, FORK_SUBTESTS и FORK_TEST_FILES. Каждый запуск будет выполнен в отдельной задаче сборочного графа.
Важно отметить, что каждый подзапуск наследует общие параметры теста из bob.make, такие как размер, таймаут, требования на ресурсы и т.д. Таким образом удобно распараллелить тесты, которые из-за своего количества перестали укладываться в таймаут, но если в bob.make стоит требование cpu(4), то при большом количестве параллельных задач, локальный запуск bob make может замедлиться.
Не все FORK-макросы поддержаны для конкретного тестового фреймворка и языка: перед их использованием, уточните текущий статус в документации.
Exec-тесты
Помимо обычных тестов, которые будут описаны в специфичных для языков разделах, есть возможность писать exec-тесты. Exec-тесты позволяют выполнить произвольную команду и убедиться, что она успешно завершается. Успешным считается завершение команды с кодом возврата 0.
Подробнее про описание Exec-тестов написано здесь.
Как описывать тесты для языков
C++
Сейчас поддержаны два тестовых фреймворка:
unittest - собственная разработка
gtest - популярное решение от Google
Также есть отдельная библиотека library/cpp/testing/common, в которой находятся полезные утилиты, независящие от фреймворка.
Бенчмарки: Используется библиотека google benchmark и модуль G_BENCHMARK. Все подключенные к автосборке бенчмарки запускаются в CI по релевантным коммитам и накапливают историю метрик.
Fuzzing: Фаззинг - это техника тестирования, заключающаяся в передаче приложению на вход неправильных, неожиданных или случайных данных. Все подробности здесь.
Linting: Поддержан статический анализ файлов с помощью clang-tidy. Подробнее про подключение своего проекта к линтингу в автосборке можно прочитать здесь.
Метрики: Помимо метрик от бенчмарков также можно сообщать числовые метрики из UNITTEST() и GTEST(). Для добавления метрик используйте функцию testing::Test::RecordProperty если работаете с gtest, или макрос UNIT_ADD_METRIC если работаете с unittest.
Python
Основным фреймворком для написания тестов на Python является pytest.
Поддерживаются Python 2 (модуль PY2TEST), Python 3 (модуль PY3TEST) и модуль PY23_TEST. Все тестовые файлы перечисляются в макросе TEST_SRCS().
Для работы с файлами, внешними программами, сетью в тестах следует использовать специальную библиотеку bobtest.
Метрики: Чтобы сообщить метрики из теста, необходимо использовать funcarg metrics.
def test(metrics):
metrics.set("name1", 12)
metrics.set("name2", 12.5)
Бенчмарки: Для бенчмарков следует использовать функцию bobtest.common.execute_benchmark(path, budget=None, threads=None). Чтобы результаты отображались в CI, результаты нужно записывать в метрики.
Канонизация: Можно канонизировать простые типы данных, списки, словари, файлы и директории. Тест сообщает о данных, которые нужно сравнить с каноническими, через возврат их из тестовой функции командой return.
Linting: Все python файлы, используемые в сборке и тестах, подключаемые через bob.make в секциях PY_SRCS() и TEST_SRCS(), автоматически проверяются flake8 линтером.
Python imports: Для программ PY2_PROGRAM, PY3_PROGRAM, PY2TEST, PY3TEST, PY23_TEST, собранных из модулей на питоне, добавлена проверка внутренних модулей на их импортируемость - import_test. Это позволит обнаруживать на ранних стадиях конфликты между библиотеками, которые подключаются через PEERDIR, а также укажет на неперечисленные в PY_SRCS файлы (но не TEST_SRCS).
Для тестов используется фреймворк JUnit версий 4.x и 5.x.
Тестовый модуль для JUnit4 описывается модулем JTEST() или JTEST_FOR(path/to/testing/module).
JTEST(): система сборки будет искать тесты в JAVA_SRCS() данного модуля.
JTEST_FOR(path/to/testing/module): система сборки будет искать тесты в тестируемом модуле.
Для включения JUnit5 вместо JTEST() необходимо использовать JUNIT5().
Содержание bob.make файла для JUNIT5() и JTEST() отличается только набором зависимостей.
Минимальный bob.make файл выглядит так:
JTEST()
JAVA_SRCS(FileTest.java)
PEERDIR(
# Сюда же необходимо добавить зависимости от исходных кодов вашего проекта
contrib/java/junit/junit/4.12 # Сам фреймворк JUnit 4
contrib/java/org/hamcrest/hamcrest-all # Можно подключить набор Hamcrest матчеров
)
END()
Java classpath clashes: Есть возможность включить автоматическую проверку на наличие нескольких одинаковых классов в Java Classpath. В проверке участвует не только имя класса, но и хэш-сумма файла с его исходным кодом, так как идентичные классы из разных библиотек проблем вызывать не должны. Для включения этого типа тестов в bob.make файл соответствующего проекта нужно добавить макрос CHECK_JAVA_DEPS(yes).
Linting: На все исходные тексты на Java, которые подключены в секции JAVA_SRCS, включён статический анализ. Для проверки используется утилита checkstyle.
Канонизация: Для работы с канонизированными данными используйте функции из devtools/jtest.
Подробная документация о тестах на Java расположена здесь.
Go
Тесты работают поверх стандартного тулинга для Go. Для работы с зависимостями теста следует использовать библиотеку library/go/test/bobtest.
Все тестовые файлы должны иметь суффикс _test.go. Они перечисляются в макросе GO_TEST_SRCS.
Тестовый модуль описывается модулем GO_TEST() или GO_TEST_FOR(path/to/testing/module).
GO_TEST(): система сборки будет искать тесты в GO_TEST_SRCS() данного модуля.
GO_TEST_FOR(path/to/testing/module): система сборки будет искать тесты в GO_TEST_SRCS в тестируемом модуле.
Минимальные bob.make файлы выглядят так:
GO_TEST()
GO_TEST_SRCS(file_test.go)
END()
Канонизация: Для работы с такими тестами используйте library/go/test/canon. Пример.
Бенчмарки: Чтобы включить бенчмарки в проекте, нужно добавить тэг bob:run_go_benchmark в bob.make проекта
Тесты с Sanitizer
bob make -t --sanitize X позволяет собирать инструментированные программы с санитайзерами. Сейчас поддерживаются: address, memory, thread, undefined, leak. Можно указывать только один санитайзер за раз.
Параметризация опций санитайзеров
Локальный запуск bob make -t --sanitize X учитывает стандартные опции для санитайзеров переданные через переменные окружения. По умолчанию тестовая машинерия добавляет всем санитайзерам опцию exitcode=100 для специальной обработки падений тестов в этой конфигурации. В UBSAN_OPTIONS дополнительно выставляются опции print_stacktrace=1,halt_on_error=1. Вы можете зафиксировать опции санитайзеров для конкретных тестов через макрос ENV() в bob.make, например: ENV(ASAN_OPTIONS=detect_stack_use_after_return=1)
Cборка с санитайзером в Sandbox
Для сборки следует использовать задачу bob_MAKE/bob_MAKE2, в которой можно выбрать требуемый санитайзер в поле Build with specified sanitizer
Запуск тестов c санитайзером в Автосборке
В автосборке подключены 2 типа cанитайзеров: memory и address, так как они достаточно легковесны и стабильны. Для подключения проекта ко всем типам cанитайзеров следует добавить свой проект в autocheck/linux/sanitizer_common_targets.inc Если требуется только memory санитайзер, то в autocheck/linux/sanitizer_memory_targets.inc Если требуется только address санитайзер, то в autocheck/linux/sanitizer_address_targets.inc
Отключение тестов от сборки с санитайзерами
Вы можете сделать проект не достижимым по рекурсам для санитайзеров, подключив проекты более гранулярно. См. предыдущий абзац.
Вы можете сделать проект не достижимым по рекурсам для санитайзеров, например:
IF (NOT SANITIZER_TYPE)
RECURSE_FOR_TESTS(
test
)
ENDIF()
Отключить конкретный тест с помощью тега bob:not_autocheck, например:
IF (SANITIZER_TYPE)
TAG(bob:not_autocheck)
ENDIF()
Отключить инструментирование конкретной библиотеки c помощью макроса NO_SANITIZE()
С помощью макроса SUPPRESSIONS() можно указать файл содержащий правила для подавления ошибок в стандартной нотации с поддержкой комментариев начинающихся с #. Механизм поддерживается для address, leak и thread санитайзеров. Пример для protobuf: bob.make tsan.supp
Важно
Добавлять исключения следует только если вы переносите известные исключения из контриба или отчётливо понимаете, что сообщение от санитайзеров ложноположительное (скорей всего нет и вам следует внимательней разобраться в проблеме). Каждое обновление кода или компилятора должно приводить к пересмотру suppression списка.
Отладка тестов санитайзера
Для отладки падающих тестов нужно собрать библиотеку с включённым санитайзером. Для этого в bob make нужно передать дополнительный флаг --sanitize=memory (на примере санитайзера памяти, см. выше). Возможно, для воспроизведения потребуется также повторить настройки оптимизации --build=release.
Для перехвата исключения под отладчиком нужно добавить точку останова: br __msan_warning_with_origin_noreturn (если не сработает, см. другие варианты тут и тут).
Под отладчиком же можно посмотреть, что санитайзер думает про каждый бит используемой памяти: call (void)__msan_print_shadow(&myVar, sizeof(myVar)) (в vscode перед командой нужно добавить -exec, вводить в окне Debug Console, а результаты смотреть в Terminal). Согласно документации единичные биты обозначают неинициализированную память.
Fuzzing
Fuzzing это техника тестирования, заключающаяся в передаче приложению на вход неправильных, неожиданных или случайных данных, совокупность которых называют корпусом. Предметом интереса являются падения и зависания, нарушения внутренней логики и проверок в коде приложения, утечки памяти, вызванные такими данными на входе.
FUZZ
Для того, чтобы начать пользоваться фаззингом в Аркадии, нужно создать FUZZ модуль с реализацией функции LLVMFuzzerTestOneInput пример. Для эффективного фаззинга целевая функция должна быть быстрой и покрывать конкретную небольшую функциональность, которая будет фаззиться.
В Аркадии поддержаны AFL и libFuzzer поверх единого интерфейса, но автоматический фаззинг пока работает только через libFuzzer.
Сборка с libFuzzer
Для сборки FUZZ модуля с libFuzzer достаточно дополнительно указать санитайзер и подсчёт sanitize покрытия - для того чтобы fuzzer понимал, смогли ли текущие входные данные привести к попаданию в новую трассу выполнения:
bob make -A --sanitize=address --sanitize-coverage=trace-div,trace-gep
На выходе получается исполняемый файл, который и есть драйвер. Тип санитайзера и тип покрытия можно менять, подробнее можно почитать в документации llvm.
Сборка c AFL
Для сборки FUZZ модуля с AFL нужно запускать сборку с sanitize-coverage=trace-pc
bob make --afl --sanitize=undefined --sanitize-coverage=trace-pc%%
В отличие от libFuzzer, для AFL нужен внешний драйвер - afl-fuzz:
bob tool afl-fuzz -i INPUT -o OUTPUT -- /path/to/binary%%
Concept
Мы разделяем fuzzing и прогон корпуса - fuzzy test.
Fuzzing - процесс поиска примеров для расширения корпуса. Корпус состоит из двух частей - пользовательской и автоматически сгенерированной. Пользовательские примеры должны лежать в директории corpus, рядом с bob.make, в виде отдельных файлов (имена не имеют значения). Эти примеры должны быть малочисленны и составлены вручную, они помогут fuzzer в поиске новых интересных случаев.
Автоматически сгенерированные данные сохраняются в виде Sandbox ресурсов и прикрепляются к проектам через специальный файл corpus.json в директории проекта в PROJECT/fuzzing. Это поведение можно отключить с помощью опции --fuzz-local-store, намайненные данные будут находится в <project_path>/test-results/fuzz/<binname>/mined_corpus.tar.
Пример локального запуска фаззинга (расширения корпуса):
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A --fuzzing
Fuzzy тест - это только прогон имеющегося корпуса (пользовательского и автоматического). Fuzzy тесты запускаются автоматически в Автосборке CI на все релевантные изменения в репозитории, которые влияют на сборку FUZZ модуля. Это позволяет проверять вносимые изменения на предмет ошибок/утечек на основе корпуса.
Пример локального запуска fuzzy теста:
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A
Отладка
Прогон конкретного кейса
Для прогона одного конкретного кейса следует воспользоваться опцией --fuzz-case:
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A --fuzz-case=abs_path
Кейc можно скачать с CI - baseunit в плашке файлов после раскрытия snippet или отдельно сохранить после локального прогона fuzzy теста.
Ручная минимизация корпуса
Если автоматическая минимизация не успевает выполниться (например из-за того, что роботы заливают слишком много данных в корпус или скорость прогона кейсов крайне мала), минимизацию можно произвести самостоятельно на разработческой машине:
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep -A --fuzzing --fuzz-minimization-only
После чего полученный корпус необходимо закоммитить.
Если корпус расширяется слишком быстро и минимизация постоянно не укладывается в таймаут, следует добавить тег в bob.make файл bob:always_minimize. В этом случае корпус будет минимизироваться после каждого запуска фаззинга. Этого же эффекта можно достичь с помощью ключа --fuzz-minimize.
Метрики
bob make собирает следующие метрики во время fuzzing, доступные при локальном прогоне при указании -P --show-metrics и в CI у fuzz:test теста:
corpus_size - зарегистрированный fuzzer'ом размер корпуса
fuzz_iterations_per_second - количество итераций в секунду. Если тест таймаутится, до повышения его размера, следует посмотреть на эту метрику, возможно в код была добавлена регрессия, которая замедлила целевую функцию
number_of_executed_units - количество проверенных кейсов
mined_corpus_size - количество новых кейсов, которые нашёл fuzzer (расширение корпуса)
peak_rss_mb - пиковое потребление rss во время fuzzing/прогона fuzzy теста
slowest_unit_time_sec - время самого медленного из запущенных кейсов
Опции по умолчанию
Для fuzzing и прогона fuzzy тестов выставляются дополнительные параметры:
Опции для libFuzzer:
-max_total_time=600
-rss_limit_mb=4096
-timeout=600
Переменные окружения *SAN_OPTIONS:
coverage=1
allocator_may_return_null=1
FUZZ_OPTS
В запуск bob make можно передать опции для fuzzing/fuzzy теста с помощью опции --fuzz-opts. Например так можно переопределить время fuzzing'a:
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep --fuzzing -A --fuzz-opts="-max_total_time=300"
Некоторые опции хочется фиксировать, чтобы fuzzing в автосборке/еженощно запускался с ними. Для этого следует использовать макрос FUZZ_OPTS, в котором следует перечислить требуемые опции через пробел. Например:
FUZZ_OPTS(
-max_len=1024
-only_ascii=1
-rss_limit_mb=2048
)
С помощью FUZZ_OPTS не следует переопределять -max_total_time, так как в этом случае фаззинг может начать падать - это значение не задаёт лимит на время работы Sandbox таски и она форсированно завершится через 3 часа.
FUZZ_DICTS
Для libFuzzer можно указать пути относительно корня Аркадии до файлов-словарей, каждый из которых содержит на каждой строке - один case, от которого может отталкиваться fuzzer движок для мутаций и поиска новых интересных входных данных. Пример такого словаря
Пример использования макроса:
FUZZ_DICTS(
devtools/test_tool/run_fuzz/tests/data/sample_with_dict/dict.txt
devtools/test_tool/run_fuzz/tests/data/sample_with_several_dicts/dict.txt
)
Fuzz proof
Для подтверждения высокого уровня покрытия кода имеющимся корпусом, можно воспользоваться режимом --fuzz-proof X, где X время в секундах, которое требуется дополнительно фаззить с момента обнаружения последнего найденного кейса в текущем запуске. Если в течении X секунд с момента обнаружения последнего кейса (или с начала фаззинга, если не было найдено ни одного нового кейса в текущем запуске) будет найден хотя бы один новый - это приведёт к завершению фаззинга с ошибкой. Все найденные кейсы будут сохранены в корпусе.
Режим следует комбинировать с ограничением по времени или по суммарному количеству итераций.
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep --fuzzing -A --fuzz-opts="-max_total_time=3600" --fuzz-proof=1800
В этом случае будет запущена стандартная процедура расширения корпуса с ограничением времени фаззинга в 1 час (3600с). После её успешного окончания, ищется самый свежий кейс в полученном корпусе.
Если машинерия фаззинга обнаружит последний найденный кейс более 30 минут (1800с) назад, то считается что proof состоялся и фаззинг успешно завершается.
Если последний кейс был обнаружен 10 минут назад (для примера), то фаззинг будет перезапущен на дополнительные 20 минут в режиме fuzz proof с использованием всего текущего корпуса (включая новые найденные данные). Если тестовая машинерия обнаружит новый кейс во время работы в этом режиме, процесс фаззинга будет прерван, а все найденные кейсы сохранены - proof не состоялся, об этом сообщит ошибка в тесте.
bob make -r --sanitize=address --sanitize-coverage=trace-div,trace-gep --fuzzing -A --fuzz-runs=1000000 --fuzz-proof=3600
Аналогично работает fuzz proof режим при запуске фаззинга с ограничением суммарного количества прогонов целевой функции.
Опции фаззинга bob make -t
Опция Описание
--fuzzing Запускает фаззинг (режим расширения корпуса), см. concept
--fuzz-opts OPTS Задаёт опции для libFuzzer, см fuzz-opts
--fuzz-case PATH Позволяет указать путь до конкретного кейса, который следует проверить, см fuzz-case
--fuzz-local-store Отключает автоматическое сохранение и загрузку расширения корпуса (новые намайненные данные) в Sandbox
--fuzz-runs X По умолчанию fuzzing запускается в режиме ограничения по времени, см max_total_time, --fuzz-runs позволяет ограничивать количество запусков целевой функции.
--fuzz-minimize Всегда минимизировать корпус после фаззинга, см minimization
--fuzz-minimization-only Запустить процедуру минимизации корпуса без фаззинга, см minimization
--fuzz-proof X Позволяет задать Х секунд, которое требуется дополнительно фаззить после последнего найденного кейса для корректного завершения фаззинга, см fuzz-proof
Контекстные файлы в тестах
Некоторым тестам для корректной работы нужно уметь взаимодействовать с тестовым окружением. Для каждого из поддержанных языков у нас есть библиотеки, которые предоставляют информацию об окружении. go python cpp java
Сами библиотеки получают информацию из контекстного файла, контент которого формируется тестовой машинерией перед запуском теста. Путь до контекстного файла хранится в переменной окружения bob_TEST_CONTEXT_FILE.
Формат
Контекстный файл - это файл в json формате, данные в котором разделены на 4 секции:
build: Содержит информацию о типе сборки, флагах и санитайзерах
resources: Содержит информацию о глобальных ресурсах, доступных тесту во время исполнения. В основном, это какое-то тулы
runtime: Содержит информацию о тестовом окружении, например, путь до корня аркадии, путь до тестового проекта, путь до build_root и т.д.
internal: Содержит информацию, необходимую для корректной работы тестовой машинерии. Данную секцию не следует использовать при написании тестов.
енерация контекстного файла
Для генерации контекстного файла достаточно запустить команду bob make -A --test-prepare тогда по завершении ее работы, контекстный файл будет лежать по пути test-results/<test_type>/test.context
Запуск тестов из бинарного файла
После запуска команды bob make -A --test-prepare в директории с тестом будет лежать тестовый бинарь. При обычных запусках, этот бинарь запускает тестовая машинерия, которая передает ему нужные параметры и путь до контекстного файла. При желании, все это можно передать вручную.
Важно
При подобном запуске Java-тестов не выставляются переменные окружения, устанавливаемые рецептами или макросом ENV.
нологию. Контейнеры обрабатывают события chunk-event, suite-event, обновляя поля отмеченные плюсом в столбце Container.
Test case - это минимальная единица запуска теста. Тесты обрабатывают события subtest-started, subtest-finished, обновляя поля отмеченные плюсом в соответствующем столбце.
Internal
Ниже представлены технические поля, которые не следует использовать напрямую, они добавляются в виде chunk-event в test_tool run_test в trace-файл после тестирования или маркируют специальные технические типы тестов.
Property Type Chunk Test case Description
nchunks integer + Указывает на общее количество chunk в suite. Допустимые значения >= 1. Добавляется при использовании макросов FORK_TESTS()/FORK_SUBTESTS()
chunk_index integer + Указывает на текущий chunk. Допустимые значения [0, nchunks). Добавляется при использовании макросов FORK_TESTS()/FORK_SUBTESTS()
chunk_filename string + Указывает на именованный chunk, когда запуск suite разбивается на на фиксированное количество чанков, а по количеству тестовых файлов. Добавляется при использовании макроса FORK_TEST_FILES()
is_diff_test string + Признак того что тест является diff тестом
Result
result у события subtest-finished содержит канонические данные в json виде. См. так же документацию про канонизацию. Сверка канонических данных или их канонизация (сохранение новых значений) происходит за границами runtime теста, а именно - в test_tool run_test после выполнения тестирования. Машинерия канонизации работает только для тестов в статусе good.
Например 'result': 12, говорит о том что тест канонизировал чисто 12, 'result': {'a': 'b'}, канонизировал словарь.
При канонизации файлов result должен иметь определённый вид:
Property Type Optional Description
uri string Указывает путь до файла в нотации file://<path>, где path - абсолютный путь до канонизируемого файла
checksum string + md5 checksum от файла. Если поле есть, то оно используется до фактической сверки контента, что ускоряет прохождение стадии сверки канонических данных для большим файлов
diff_tool path + см. описание канонизации
local boolean + см. описание канонизации
diff_file_name string + см. описание канонизации
diff_tool_timeout integer + см. описание канонизации
Пример поддержки канонизации в go.
Contract
Есть определённый контракт между тестовой машинерией и тестовым фреймворком (или враппером при слабой интеграции), который нужно иметь в виду:
trace-файл append-only. Тестовая машинерия открывает хендлер на чтение и читает данные из trace-файла по мере поступления. Поэтому нельзя изменять уже записанные данные или заменять файл через rename для атомарного внесения изменений. Это не будет работать и может вызвать нарушения внутренней логики. Для обновления каких-либо значений, нужно порождать соответствующее событие с новым значением требуемого поля.
Тестовый фреймворк (или враппер) перед началом тестирования должен сдампить список всех тестов, которые будут запускаться в рамках тестирования (после фильтрации), в виде событий subtest-finished c полными именам тестов (class и subtest) в статусе not_launched. Это гарантирует отсутствие мигания множества тестов в случае аварийного завершения тестирования. Т.е. множество тестов в чанке не должно меняться, не зависимо от результатов (abort, timeout, etc). По мере прохождения тестов, в trace-файл будут попадать события для not_launched тестов с новыми актуальными статусами, которые будут его обновлять.
Строки содержащие некорректный json отбрасываются и логгируются тестовой машинерией в виде base64 записи для последующего разбора.
Тесты : запуск произвольных программ
Данный тип тестов позволяет выполнить произвольную команду и убедиться, что она успешно завершается.
Примечание
Примеры exec-тестов можно найти здесь.
Успешным считается завершение команды с кодом возврата 0.
Стандартные программы unix и команды shell не доступны, некоторые аналоги можно подключить через DEPENDS.
Простое описание теста в bob.make выглядит так:
OWNER(g:some-group)
EXECTEST() # Объявляем Exec-тест
RUN( # Команда, которую хотим выполнить
cat input.txt
)
DATA( # Тестовые данные (здесь лежит input.txt)
PROJECT/devtools/bob/test/tests/exectest/data
)
DEPENDS( # Зависимость от других проектов (здесь лежат исходные коды cat)
devtools/dummy_PROJECT/cat
)
# Текущий каталог для теста (каталог с input.txt)
TEST_CWD(devtools/bob/test/tests/exectest/data)
END()
В общем случае в одном bob.make можно объявить несколько разных команд:
OWNER(g:some-group)
EXECTEST()
RUN( # Первый тест
NAME test-1 # Явное объявление имени теста
echo "1"
)
RUN( # Второй тест
NAME test-hello-world
echo "Hello, world!"
)
END()
Каждое объявление макроса RUN - это отдельный тест. Тесты выполняются в том порядке, в котором перечислены в файле.
Важно
При параллельном запуске тестов в каждый из параллельно выполняемых потоков попадает только часть команд, указанных в bob.make. Поэтому не рекомендуется писать команды, результаты выполнения которых зависят от выполнения команд из вышестоящих макросов RUN.
В общем виде макрос RUN предоставляет множество других возможностей:
OWNER(g:some-group)
EXECTEST()
RUN(
NAME my-test # Имя теста
ENV TZ=Europe/Moscow
ENV LANG=ru_RU.UTF-8 # Переменные окружения
echo "1" # Команда и ее флаги
STDIN {PROJECT_ROOT}/my-project/filename.txt # Файл, который подается с stdin команде
STDOUT {TEST_CASE_ROOT}/test.out # Куда сохранить stdout команды
STDERR {TEST_CASE_ROOT}/test.err # Куда сохранить stderr команды
CWD {PROJECT_BUILD_ROOT}/my-project # Рабочий каталог теста
CANONIZE {TEST_CASE_ROOT}/test.out # Путь до файла с эталонными данными (будет сохранен в Sandbox)
CANONIZE_LOCALLY {TEST_CASE_ROOT}/test.out # Путь до файла с эталонными данными (будет сохранен в подкаталог canondata)
CANONIZE_DIR {TEST_CASE_ROOT}/dir # Путь до директории с эталонными данными (будет сохранён в Sandbox)
CANONIZE_DIR_LOCALLY {TEST_CASE_ROOT}/dir # Путь до директории с эталонными данными (будет сохранен в подкаталог canondata)
DIFF_TOOL my-project/tools/my-difftool/my-difftool # Путь до исполняемого файла, используемого для сравнения вывода теста с эталонными данными
DIFF_TOOL_TIMEOUT # Таймаут на запуск diff_tool
)
DEPENDS(
my-project/tools/my-difftool
)
END()
Для указания путей доступны следующие переменные:
Переменная Описание
{PROJECT_BUILD_ROOT} Корень сборочной директории
{PROJECT_ROOT} Корень единого репозитория
{TEST_SOURCE_ROOT} Путь к каталогу, в котором находится bob.make-файл текущего теста
{TEST_CASE_ROOT} Путь к каталогу с результатами текущего теста
{TEST_WORK_ROOT} Путь к рабочему каталогу теста
{TEST_OUT_ROOT} Путь к каталогу с результатами прохождения всех тестов (результаты каждого теста лежат во вложенном каталоге)
Внимание
Программы, подключенные по DEPENDS не складываются в Аркадию, после исполнения теста там может оказаться симлинк, но во время исполнения теста программы там нет. Не используйте {PROJECT_ROOT} для указания пути до запускаемой программы. Бинари обычно доступны вообще без указания пути, но путь от корня Аркадии (без указания корня) тоже сработает.
Полезные программы
not - инвертирует выходной код нормально завершившейся программы. Иногда нужно проверить, что тестируемая программа при нужных условиях завершается с ненулевым кодом (но не падает и не выходит по сигналу). Не стоит злоупотреблять такими тестами. Пример:
RUN(
NAME "my_program fails with wrong arguments"
not my_program wrong arguments
)
DEPENDS(
my_project/my_program
tools/not
)
Проверки кода и корректности данных
Система сборки поддерживает разнообразные проверки стиля и кода. Большинство из них является opt-out, т.е. подключаются автоматически, с возможностью отключения. bob test / bob make -t поддерживает ключ --style, который запускает только style-тесты и легковесные проверки, к которым не относятся регулярные тесты.
Если запускаемые проверки не зависят от сборки, то bob test --style запустит только линтеры, без сборки.
Примечание
Все style-тесты по умолчанию кешируются, что эквивалентно команде bob test --cache-tests. Это значит, что при перезапуске bob test без изменения исходного кода style-тесты не будут перезапускаться, а вернут результат из кеша. Это сделано для ускорения локального прогона style-тестов, которые являются быстрыми и стабильными. Для принудительного перезапуска тестов нужно добавить ключ --retest.
python
flake8
Все python-файлы, используемые в сборке и тестах, подключаемые через bob.make в секциях PY_SRCS и TEST_SRCS, автоматически проверяются линтером flake8.
Допустимая длина строки установлена в 200 символов.
В редких случаях можно игнорировать конкретные строки целиком, указывая конкретные коды ошибок, добавляя к нужной строке комментарий # noqa или # noqa: E101.
Внутри __init__.py можно подавлять только ошибку F401 Module imported but unused с помощью комментария # flake8 noqa: F401 в начале файла.
Примечание
Только для директории contrib допустимо отключение проверки стиля макросом NO_LINT().
В Аркадии используется единый конфиг, через который включаются некоторые плагины. Изменения в конфиге должны быть согласованы с python-com@.
Важно
Если локально у вас есть ошибки flake8, которых нет в Автосборке/CI, скорей всего вы столкнулись с вашим техдолгом, см. документацию о миграциях.
Для временного отключения подобных ошибок используйте запуск BOB_TEST_DISABLE_FLAKE8_MIGRATIONS=0 bob test и запланируйте починку.
Коды ошибок описаны на следующих страницах:
flake8rules.com
pypi.org/project/flake8-commas
pydocstyle.org
bandit.readthedocs.io
black
Для проектов на python3 можно добавить макрос STYLE_PYTHON(), который будет генерировать тест, проверяющий соответствие кода в модуле аркадийному style guide. В качестве линтера использует black c минимальным конфигом, который применяется в bob style.
Примечание
Макрос STYLE_PYTHON() можно указывать только для типов модулей PY3* и PY23*.
Быстро добавить макрос в проект можно командой:
cd <project>
bob project macro add STYLE_PYTHON --recursive --quiet --after PY3_LIBRARY --after PY23_LIBRARY --after PY3TEST --after PY23_TEST --after PY3_PROGRAM
Для запуска только black тестов внутри проекта используйте команду bob test --test-type black.
Для автоматического применения bob style в PyCharm см. заметку от srg91.
python imports
Для программ PY2_PROGRAM, PY3_PROGRAM, PY2TEST, PY3TEST, PY23_TEST, собранных из модулей на Python, есть проверка внутренних модулей на их импортируемость - import_test. Импорт-тесты не являются style-тестами и не входят в множество тестов --style, так как требуют сборку. Импорт-тесты обнаруживают на ранних стадиях конфликты между библиотеками, которые подключаются через PEERDIR, а также указывают на неперечисленные в PY_SRCS файлы (но TEST_SRCS не проверяется).
Проверять герметичность каждого модуля python достаточно дорого - требуется сборка python и его компоновка с целевым модулем только для одного теста, поэтому проверка импортируемости добавляется только для исполняемых программ, которые будут работать в таком виде на production. По этой причине проверяются только модули, подключаемые к сборке через PY_SRCS, но не через TEST_SRCS.
Проверку можно отключить c помощью макроса NO_CHECK_IMPORTS. Он принимает список масок модулей, которые не нужно проверять:
NO_CHECK_IMPORTS(
devtools.pylibrary.*
)
В программах можно написать NO_CHECK_IMPORTS() без параметров, чтобы полностью отключить проверку импортов.
Бывает, что в общих библиотеках импорты происходят по условию, например в psutil:
if sys.platform.startswith("win32"):
import psutil._psmswindows as _psplatform
При этом сама библиотека остаётся работоспособной, но импорт-тест будет падать, потому что проверяет все модули, влинкованные в бинарник, а у psutil._psmswindows есть зависимость от Windows. В таком случае надо написать:
NO_CHECK_IMPORTS(
psutil._psmswindows
)
Примечание
Информацию о времени загрузки импортированных модулей после исполнения импорт-теста записывается в файл *.import_test.out соответсвующего теста. В конце файла можно найти список самых медленных импортов, исполненных в тесте.
Примечание
Если информации о времени исполнения импортов недостаточно и хочется получить больше деталей о причинах медленной загрузки модулей, то можно отпрофилировать основную точку входа импорт-теста, используя ваш любимы профайлер (например cProfile).
java
java style
На все исходные тексты на Java, которые подключены через bob.make в секции JAVA_SRCS, запускаются автоматические проверки java codestyle. Проверки осуществляются при помощи утилиты [checkstyle](http://checkstyle.sourceforge.net checkstyle) версии 7.6.1.
Мы поддерживаем 2 уровня "строгости" - обычный и строгий (большее количество проверок). Строгий уровень включается макросом LINT(strict) в bob.make.
Конфигурационные файлы checkstyle для обычных и строгих проверок находятся в директории resource, табличка с описанием находится тут.
java classpath clashes
Есть опциональная проверка дублирующихся классов в classpath при компиляции java проекта. Проверяется имя класса и хэш файла с его исходным кодом, то есть идентичные классы из разных библиотек проблем вызывать не должны. Проверка включается макросом CHECK_JAVA_DEPS(yes) в bob.make.
kotlin
ktlint
Автоматически добавляется к проектам на Kotlin, которые используют нашу систему сборки. Подробнее про утилиту можно узнать тут.
Для отключения ktlint достаточно в bob.make файле модуля написать NO_LINT(ktlint).
При запусках ktlint использует корневой .editorconfig.
Исключения ktlint
Для проекта можно настроить временные исключения из правил.
Применять исключения нужно крайне редко в сложных случаях, например, при миграциях на новый ktlint и при наличии множества ошибок, которые нельзя исправить автоматически после выполнения команды ktlint -F.
В случае применения исключений разработчик обязан создать тикет на исправление всех ошибок в исключении и запланировать его выполнение.
Как добавить исключения в проверки:
Создать файл с исключениями путем выполнения команды из корня аркадии bob tool ktlint {path-to-project} --baseline={path-to-project}/{path-to-file}.
Создать тикет в очередь проекта на исправление всех ошибок в исключении.
Добавить в bob.make проекта макрос с указанием относительного пути до файла с исключениями и ссылку на тикет с исправлениями. Например, KTLINT_BASELINE_FILE(ktlint-baseline.xml)
go
gofmt
Автоматически добавляется к проектам на go, является проверкой стиля.
govet
Автоматически добавляется к проектам на go, является статической проверкой, позволяющей находить подозрительные конструкции и плохие практики. Подробнее см. документацию.
cpp
clang tidy
Clang-tidy запускается с помощью bob test -DTIDY, так как требует полностью другой граф сборки. Подробнее см. отдельный раздел про clang tidy.
frontend
eslint
Автоматически добавляется к проектам на TypeScript, использует ESLint. Проверяет стиль кода и типичные ошибки. Набор правил лежит здесь. Для отключения eslint достаточно в bob.make файле модуля написать NO_LINT().
Тесты : общие макросы
Зависимости
DEPENDS()
DEPENDS(path1 [path2...])
Указывает зависимости на другие проекты, которые нужно собрать и результаты которых должны быть доступны тесту. В параметрах перечисляются относительные пути от корня PROJECT.
Параметры suite
SIZE()
SIZE(SMALL | MEDIUM | LARGE)
Задаёт размер suite. Сейчас существует три размера:
SMALL - максимальный таймаут 60s. Размер по умолчанию.
MEDIUM - максимальный таймаут 600s.
LARGE - максимальный таймаут 3600s.
Важно
Если в bob.make листе указаны несколько значений макроса SIZE(), например, через вложенный INCLUDE, то последнее значение переопределяет все предыдущие.
TIMEOUT()
TIMEOUT(time)
Устанавливает таймаут на запуск всей сьюиты. По умолчанию равен:
60s (1min) для SMALL тестов;
600s (10min) для MEDIUM тестов;
3600s (1hour) для LARGE тестов.
Примечание
Таймаут не может быть больше, чем максимальная длительность теста данного размера.
Если запуск сьюиты разбит на части (FORK_TEST_FILES()/FORK_TESTS()/FORK_SUBTESTS()), то указанный таймаут ограничивает время работы каждого запуска в отдельности.
TAG()
TAG(tag1 [tag2...])
Позволяет задать пользовательские теги, по которым можно фильтровать запуск suite в bob test, см. опцию --test-tag.
Ниже представлен список специальных системных тегов, которые могут менять поведение тестов.
Запуск тестов:
bob:always_minimize: приводит к постоянной минимизации корпуса после фаззинга, см подробности в документации к fuzzing.
bob:manual: тест не будет запускаться, если явно не указано запускать тесты с таким тегом
bob:norestart: тест не будет перезапускаться при определенных ошибках
bob:not_autocheck: не запускать тест при проверке pull requests
Логирование:
bob:full_logs: приводит к падению сьюиты, если размер логов привысил 100Мб
bob:huge_logs: увеличивает ограничение на размер логов теста с 100Мб до 1Гб
bob:sys_info: добавляет вывод системной информации в лог до и после выполнения всех рецептов и тестов
bob:trace_output: включает логирование создаваемых файлов и их размеров при помощи системного вызова ptrace. Работает только под Linux. Может замедлять тестирование
bob:dump_node_env: печатает дерево директорий относительно build_root узла сразу после его запуска в лог test-results/<suite>/run_test.log. Позволяет узнать чистое окружение тестового узла, которое приехало по зависимостям
bob:dump_test_env: печатает дерево директорий относительно build_root узла непосредственно перед запуском враппера тестов в лог test-results/<suite>/run_test.log. Позволяет узнать окружение в котором будут запускаться тесты (после запуска рецептов). Не работает вместе с bob:dirty, так как привело бы к полному обходу PROJECT.
large-тесты:
bob:fat: помечает тест как LARGE
bob:force_distbuild: запускает тест в distbuild вне зависимости от его размера
bob:force_sandbox: запускает тест в Sandbox. Используется только вместе с тегом bob:fat
bob:noretries: тесты с таким тегом не будут запускаться в автосборке повторно
bob:privileged: запускает тесты в Sandbox в контейнере от имени root
bob:sandbox_coverage: включает LARGE тесты в подсчет покрытия. Нужно использовать вместе с bob:force_sandbox
bob:relwithdebinfo: тесты с таким тэгом будут собираться с флагом --build relwithdebinfo - релизная сборка с включенными ассертами для C++. Тесты, собранные с --build relwithdebinfo, могут исполняться медленне, чем для релизной сборки, но ассерты могут дать больше полезной информации.
Управление окружением:
bob:copydata: Пути, указанные в макросе DATA() подключаются в тестовое окружение не симлинками, а рекурсивно копируются, при этом всем файлам и каталогам выставляются права на запись пользователя и группы
bob:copydataro: Аналогично bob:copydata, но наоборот, всем файлам и каталогам запрещается запись для пользователя, группы и других
Остальные:
bob:external: уведомляет систему, что тест использует внешние системы (сеть, внешние базы данных). Это значит, что такой тест потенциально нестабильный. Уведомления о поломках таких тестов будут приходить только владельцам теста и не будут приходить авторам комита, на котором тест сломался
bob:no_graceful_shutdown: завершает выполнение процесса с тестами при помощи сигнала SIGQUIT вместо SIGTERM. Это позволяет, например, поймать стектрейс состояния, в котором находится тест
bob:notags: используется для фильтрации тестов, не имеющих тегов
Sandbox-теги:
sb:XXXX: позволяет задать набор тегов для выбора агента Sandbox. При указании нескольких тегов sb:, они соединяются через логическое ИЛИ
sb:ttl=inf: позволяет задать TTL в днях для создаваемого в таске bob_MAKE ресурса BUILD_OUTPUT. Тег следует использовать, если автозапуск LARGE тестов от лица вашего робота потребляет много дисковой квоты из-за больших выходных данных в этом ресурсе. TTL по умолчанию равен 14 дней
sb:logs_ttl=14: позволяет задать TTL в днях для создаваемого в таске bob_MAKE ресурса TASK_LOGS. Тег следует использовать, если автозапуск LARGE тестов от лица вашего робота потребляет много дисковой квоты из-за больших выходных данных в этом ресурсе. TTL по умолчанию равен 14 дней
sb:store_output_binaries: сохраняет собранные бинари large-тестов в ресурсах типа BUILD_OUTPUT таски bob_MAKE
REQUIREMENTS()
REQUIREMENTS(
[cpu:<count>]
[disk_usage:<size>]
[ram:<size>]
[ram_disk:<size>]
[container:<id>]
[network:<restricted|full>]
[dns:<default|local|dns64>])
[bobv:<ENV_NAME>=<value|file>:<owner>:<vault key>]
)
Параллельный запуск тестов
FORK_TESTS()
FORK_TESTS(mode)
Разбивает запуск suite на несколько chunk'ов. По умолчанию количество chunk'ов равно 10. Это значение можно изменить с помощью макроса SPLIT_FACTOR(x). В отличие от FORK_SUBTESTS считает тесты, объединенные в один класс, неделимой сущностью.
Каждый chunk наследует общие параметры теста из bob.make: размер, таймаут, требования на ресурсы. Поэтому этот макрос удобно использовать, чтобы распараллелить тесты, которые не укладываются в таймаут.
Параметр mode определяет, каким образом будет происходить распределение тестов по chunk'ам. Может быть равен SEQUENTIAL или MODULO. Если аргумент не указан, то по умолчанию равен SEQUENTIAL. SEQUENTIAL распределяет тесты равными диапозонами, предварительно отсортировав их по имени. MODULO разделяет тесты по модулю, предварительно их отсортировав.
FORK_SUBTESTS()
FORK_SUBTESTS(mode)
Разбивает запуск suite на несколько chunk'ов. По умолчанию количество chunk'ов равно 10. Это значение можно изменить с помощью макроса SPLIT_FACTOR(x). В отличие от FORK_TESTS может разбивать тесты, объединенные в один класс, в разные chunk'и. Нежелательно использовать, если у классов есть тяжелые подготовительные стадии.
Каждый chunk наследует общие параметры теста из bob.make: размер, таймаут, требования на ресурсы. Поэтому этот макрос удобно использовать, чтобы распараллелить тесты, которые не укладываютсмя в таймаут.
Параметр mode определяет, каким образом будет происходить распределение тестов по chunk'ам. Может быть равен SEQUENTIAL или MODULO. Если аргумент не указан, то по умолчанию равен SEQUENTIAL. SEQUENTIAL распределяет тесты равными диапозонами, предварительно отсортировав их по имени. MODULO разделяет тесты по модулю, предварительно их отсортировав.
FORK_TEST_FILES()
FORK_TEST_FILES()
Разбивает прогон тестов из suite на количество chunk'ов, равное количеству файлов с тестами, перечисленных в bob.make. Каждый запуск работает только с одним конкретным файлом.
Можно комбинировать вместе с FORK_TESTS() и FORK_SUBTESTS(). В этом режиме будет происходить дополнительное разбиение тестов для каждого файла.
Важно
Поддержан только для pytest.
SPLIT_FACTOR()
SPLIT_FACTOR(x)
Изменяет количество chunk'ов при параллельном запуске тестов. Можно использовать только с FORK_TESTS() и FORK_SUBTESTS().
Специфичные макросы
USE_RECIPE()
USE_RECIPE(path [arg1 arg2...])
Подключает рецепт для настройки тестового окружения к тесту. Если тест описан с использованием макросов FORK_TEST() / FORK_SUBTESTS(), то рецепт будет подготавливать отдельное окружение для каждого запуска.
SKIP_TEST()
SKIP_TEST(Reason)
Отключает тесты для всей модульной сьюиты. Параметром указывается причина отключения.
NO_LINT()
NO_LINT()
Отключает codestyle проверки для java и python."
Вот файл справки команды bob test "bob test --help
Build and run all tests
bob test is alias for bob make -A
Usage:
bob test [OPTION]... [target]...
Examples:
bob test Build and run all tests
bob test -t Build and run small tests only
bob test -tt Build and run small and medium tests
bob test -L Print test names, don't run them
bob test -F "subname" Build and run test which name contains "subname"
Options:
bob operation control
-h, ~~help Print help. Use -hh for more options and -hhh for even more.
--rebuild Rebuild all
-C=BUILD_TARGETS, --target=BUILD_TARGETS
Targets to build
-k, --keep-going Build as much as possible
-j=BUILD_THREADS, --threads=BUILD_THREADS
Build threads count (default: 2)
--clear Clear temporary data
Build output
--add-result=ADD_RESULT
Process selected build output as a result
--add-protobuf-result
Process protobuf output as a result
--add-flatbuf-result
Process flatbuf output as a result
--replace-result Build only --add-result targets
--force-build-depends
Build by DEPENDS anyway
-R, --ignore-recurses
Do not build by RECURSES
--no-src-links Do not create any symlink in source directory
-o=OUTPUT_ROOT, --output=OUTPUT_ROOT
Directory with build results
Printing
--stat Show build execution statistics
-v, --verbose Be verbose
-T Do not rewrite output information (ninja/make)
Platform/build configuration
-d Debug build
-r Release build
--build=BUILD_TYPE Build type (debug, release, profile, gprof, valgrind, valgrind-release, coverage, relwithdebinfo, minsizerel, debugnoasserts, fastdebug) https://docs.bobndex-team.ru/bob-make/usage/bob_make/#build-type (default: debug)
--sanitize=SANITIZE Sanitizer type(address, memory, thread, undefined, leak)
--race Build Go projects with race detector
-D=FLAGS Set variables (name[=val], "yes" if val is omitted)
--host-platform-flag=HOST_PLATFORM_FLAGS
Host platform flag
--target-platform=TARGET_PLATFORMS
Target platform
--target-platform-flag=TARGET_PLATFORM_FLAG
Set build flag for the last target platform
Local cache
--cache-stat Show cache statistics
--gc Remove all cache except uids from the current graph
--gc-symlinks Remove all symlink results except files from the current graph
YT cache
--no-yt-store Disable YT storage
Testing
Run tests
-t, --run-tests Run tests (-t runs only SMALL tests, -tt runs SMALL and MEDIUM tests, -ttt runs SMALL, MEDIUM and FAT tests)
-A, --run-all-tests Run test suites of all sizes
-L, --list-tests List tests
Filtering
-X, --last-failed-tests
Restart tests which failed in last run for chosen target
-F=TESTS_FILTERS, --test-filter=TESTS_FILTERS
Run only test that matches . Asterics '' can be used in filter to match test subsets. Chunks can be filtered as well using pattern that matches '[] chunk'
--style Run only style tests and implies --strip-skipped-test-deps (classpath.clash clang_tidy eslint gofmt govet java.style ktlint py2_flake8 flake8 black ruff tsc_typecheck). Opposite of the --regular-tests
--regular-tests Run only regular tests (benchmark boost_test exectest fuzz g_benchmark go_bench go_test gtest hermione java jest py2test py3test pytest unittest). Opposite of the --style
Console report
-P, --show-passed-tests
Show passed tests
Canonization
-Z, --canonize-tests
Canonize selected tests
Debugging
--pdb Start pdb on errors
--gdb Run c++ unittests in gdb
--dlv Run go unittests in dlv
--test-debug Test debug mode (prints test pid after launch and implies --test-threads=1 --test-disable-timeout --retest --test-stderr)
Runtime environment
--test-param=TEST_PARAMS
Arbitrary parameters to be passed to tests (name=val)
--autocheck-mode Run tests locally with autocheck restrictions (implies --private-ram-drive and --private-net-ns)
Test uid calculation
--cache-tests Use cache for tests
--retest No cache for tests
Test dependencies
-b, --build-all Build targets that are not required to run tests, but are reachable with RECURSE's
File reports
--junit=JUNIT_PATH Path to junit report to be generated
Tests over YT
--run-tagged-tests-on-yt
Run tests marked with bob:yt tag on the YT
Tests over Sandbox
--run-tagged-tests-on-sandbox
Run tests marked with bob:force_sandbox tag on the Sandbox
Coverage
--python-coverage Collect python coverage information
--ts-coverage Collect ts coverage information
--go-coverage Collect go coverage information
--java-coverage Collect java coverage information
--clang-coverage Clang's source based coverage (automatically increases tests timeout at 1.5 times)
--coverage-report Build HTML coverage report (use with --output)
--nlg-coverage Collect Alice's NLG coverage information
Fuzzing
--fuzzing Extend test's corpus. Implies --sanitizer-flag=-fsanitize=fuzzer
--fuzz-case=FUZZ_CASE_FILENAME
Specify path to the file with data for fuzzing (conflicting with "~~fuzzing")
Pytest specific
--test-log-level=TEST_LOG_LEVEL
Specifies logging level for output test logs ("critical", "error", "warning", "info", "debug")
Hermione specific
--hermione-config=HERMIONE_CONFIG
Path to configuration file
--hermione-browser=HERMIONE_BROWSERS
Run tests only in specified browser
Java-specific
--sonar Analyze code with sonar.
--maven-export Export to maven repository"
Запомни Всю эту информацию.
Вот структура документации с кратким описанием
"
1. Введение
Команда bob test используется для сборки и запуска всех тестов в проекте. Это аналог команды bob make -A, но с дополнительными функциями для тестирования.
Основные возможности команды включают:
Запуск различных типов тестов
Фильтрация тестов по различным критериям
Параллельный запуск тестов
Управление выводом результатов тестов
2. Основные понятия
Test: Именованная проверка, описанная в коде на поддерживаемом языке программирования.
Chunk: Запуск программы с тестами, в рамках которой исполняются тесты.
Suite: Набор из нескольких тестов одного типа и с одинаковым набором зависимостей.
3. Локальная работа с тестами
Запуск тестов: По умолчанию тесты запускаются в конфигурации debug.
Директория результатов тестов: Тестовые результаты хранятся в директории test-results.
Режимы сборки: Локальная сборка производится в конфигурации debug, в автосборке - relwithdebinfo.
4. Запуск тестов
Опции запуска:
-t: Запускает маленькие (SMALL) тесты.
-tt: Запускает маленькие (SMALL) и средние (MEDIUM) тесты.
-ttt: Запускает маленькие, средние и большие (LARGE) тесты.
-A, --run-all-tests: Запускает все тесты независимо от размера.
5. Управление выводом результатов
-L, --list-tests: Выводит список тестов без их запуска.
--fail-fast: Прекращает выполнение тестов при первой неудаче.
6. Выборочное тестирование
--test-filter=TESTS_FILTERS: Ограничивает тесты, соответствующие указанным фильтрам.
--test-tag=TEST_TAGS_FILTER: Запускает тесты с указанными тегами.
--test-size=TEST_SIZE_FILTERS: Фильтрация по размеру теста.
--test-type=TEST_TYPE_FILTERS: Фильтрация по типу теста.
7. Параллельный запуск тестов
Макросы для параллельного тестирования:
FORK_TESTS
FORK_SUBTESTS
FORK_TEST_FILES
8. Фильтрация тестов
Фильтрация Suite и отдельных тестов.
9. Канонизация (и переканонизация) тестов
Команда поддерживает сравнение с эталонными данными. Используйте опцию -Z для канонизации тестов.
10. Описание тестов в bob.make
Примеры макросов и описание, как описывать тесты для основных языков программирования.
11. Тесты для языков программирования
Описание тестов для:
C++
Python
Java
Go
12. Тесты с Санитайзером
Описание и примеры использования санитайзеров для тестов.
13. Fuzzing
Описание фаззинга, примеры использования и метрики.
14. Запуск произвольных программ (Exec-тесты)
Описание, как писать и запускать Exec-тесты.
15. Проверки кода и данных
Описание проверок для различных языков:
Python (flake8, black)
Java
Kotlin (ktlint)
Go (gofmt, govet)
C++
Frontend (eslint)
16. Общие макросы
Описание макросов: DEPENDS, SIZE, TIMEOUT, TAG, REQUIREMENTS, FORK_TESTS, FORK_SUBTESTS, FORK_TEST_FILES
17. Опции команды bob test
Перечень опций с кратким описанием:
-h, --help: Вывести справку
-t, --run-tests: Запуск тестов
-A, --run-all-tests: Запуск всех тестов
-L, --list-tests: Листинг тестов
-F, --test-filter: Фильтрация тестов
-P, --show-passed-tests: Показать пройденные тесты
-Z, --canonize-tests: Канонизация тестов
Множество других опций для настройки окружения и управления тестами
"
Напиши подробно "5. Управление выводом результатов" по изученному тобой тексту