Измерение уровня покрытия кода тестами в Raku | Code coverage level measurement in Raku
Легко заметить одну особенность в культуре написания модулей на Raku и Perl — почти всегда присутствует папка t с авто тестами. Даже в маленьких проектах. Конечно, количество и качество тестов может варьироваться, но чаще всего они есть.
Когда мы говорим об авто тестировании, возникает желание как-то оценить «качество» написанных тестов. Одним из критериев может являться процент исполненных строк (веток) кода во время успешного прохождения тестов. На данный момент для Raku это можно сделать двумя способами. 
Первый — с помощью CommaIDE (https://commaide.com), IDE для разработки на языке Raku. CommaIDE это отличная вещь, особенно, после последних релизов. Я настоятельно рекомендую её попробовать. В платной версии IDE есть возможность запустить тесты, и посчитать процент покрытия кода. Кроме того, для наглядности, код раскрасится в соответствующие цвета.
Второй — использовать модуль App::RaCoCo (Raku Code Coverage). Он поставляет консольное приложение racoco, которое запускает тесты, и подсчитывает процент покрытия. Сегодня хочется рассказать об этом модуле подробнее.
Как это работает в теории
Чтобы посчитать покрытие, нам нужно знать две вещи: какие именно строки кода потенциально могут выполниться, и фактически выполненные во время прохождения тестов.
NB: существуют несколько бекендов для компилятораRaku(например,jvm,js,moarvm). На данный момент предоставить всю необходимую информацию может толькоMoarVM. Кроме того, более информативно считать не выполненные строки кода, а ветви. Например, в одной строкеsay 'second' if $first;две ветви. К сожалению, сейчас дажеMoarVMне предоставляет информации о выполненных ветвях. По-этому, далее будет говориться только о выполненных строках.
Как это работает на практике
Чтобы получить список потенциально исполняемых строк кода некоторого файла, нужно:
- получить байт-код этого файла. Его можно или найти в папке lib/.precomp, или скомпилировать самостоятельно. Например,raku -Ilib --target=mbc --output=result lib/source-file.rakumod;
- снять дамп с этого файла командой moar --dump result. Вхождения, начинающиеся со словаannotation, заканчиваются номером строки, которая может быть исполнена в соответствующем модуле. Например:
[...]
00013      bindlex            lex_Frame_9_self_obj, loc_19_obj
00014      param_sn           loc_4_obj
     annotation: lib/source-file.rakumod (MyModuleName):9
00015      getcode            loc_1_obj, Frame_10
00016      getcode            loc_2_obj, Frame_11
[...]Чтобы получить данные о том, какие строки выполнились, нужно:
- положить в переменные окружения специальный флаг MVM_COVERAGE_LOG. Его значением должен являться путь к файлу с будущими логами исполнения кода;
- запустить тесты;
- разобрать файл с логами. Нас интересуют только строки с файлами из тестируемой библиотеки. Например:
HIT src/vm/moar/ModuleLoader.nqp 133 HIT src/vm/moar/ModuleLoader.nqp 5 HIT lib/source-file.rakumod (MyModuleName) 1 HIT lib/source-file.rakumod (MyModuleName) 9 HIT gen/moar/World.nqp 5240 HIT gen/moar/World.nqp 5240
Имея эти данные и немного знаний об арифметике можно посчитать результат.
Как это использовать
Рассмотрим несколько типичных вариантов использования racoco.
MyModyle> racoco
t/00-simple.t ................... ok 
t/01-difficult.t ................ ok  
All tests successful.
Files=2, Tests=54,  3 wallclock secs
Result: PASS                        # prove6 result 
Coverage: 70.6%                     # code coverage percentage
MyModule> racoco --exec='prove --exec=raku'
t/00-simple.t ................... ok 
t/01-difficult.t ................ ok
All tests successful.
Files=2, Tests=54, 3 wallclock secs
Result: PASS                        # prove result
Coverage: 70.8%
MyModule> racoco --exec='prove6 -l' # this is for who do not write
                                    # 「use lib 'lib';」 in tests
[...]
MyModule> racoco --silent
Coverage: 70.8%          # just what it was all about
MyModule> racoco --html  # HTML with statistics and colored code
Visualisation: file://.racoco/report.html
Coverage: 70.8%MyModule> racoco --/exec --html --color-blind
            # don't run tests, use the latest data
            # and color code for colorblind peopleMyModule> racoco --fail-level=93   # the launch should fail
[...]                              # in case of low coverage
Coverage: 70.8%      # exit code: 23 (93 - 70 = 23)
MyModule> racoco --silent
Coverage: 70.8%
MyModule> racoco --silent --append --exec='prove6 xt'
Coverage: 95.2%  # combined coverage of both launches
                 # similarly --exec='prove6 t xt'
/root/  > racoco --lib='/home/user/raku/MyModule/lib' \
                 --exec='prove6 /home/user/raku/MyModule/t'
                 # launch not from the module folder
MyModule> racoco --raku-bin-dir='/home/user/hack-rakudo/install/bin'
               # explicit indication of the path to raku and moarНеочевидный момент
racoco может выбросить сообщение об ошибке про неочевидное содержимое папки .precomp. Это происходит из-за того, что папка содержит подпапки для каждой использованной версии компилятора. В общем случае, пользователь может выбрать любую версию компилятора для запуска тестов. При этом, если нет необходимости, скомпилированные ранее файлы могут быть не обновлены. Таким образом, racoco не может угадать какую папку анализировать. Для подобных случаев существует флаг --fix-compunit, который удалит всё содержимое папки .precomp самостоятельно (если вам дорого время компиляции, то можно уничтожить неактуальные версии скомпилированных файлов вручную).
Немного про CI
Невозможно говорить про авто тесты и покрытие кода и не заговорить про CI. В этом контексте измерение покрытия кода может использоваться для нескольких вещей:
- Падение сборки в случае снижения покрытия ниже определённого уровня;
- Падение сборки в случае снижения покрытия относительно предыдущих значений;
- Простое измерение покрытия кода как характеристики.
Для примера, рассмотрим то, как можно добавить шилдик об уровне покрытия кода в README файл на GitHub.
Есть замечательная статья о том, как настроить автоматическую проверку кода тестами с помощью GitHub Actions для проектов на Raku. Первый YAML из этой статьи отлично работает. Нужно заменить последний шаг (Run Tests), на следующую последовательность:
- Установить App::RaCoCo. Этот модуль намеренно не содержит никаких зависимостей, чтобы его установка занимала как можно меньше времени;- name: Install App::RaCoCO run: zef install --/test App::RaCoCo
- Запустить racoco;- name: Run App::RaCoCo run: racoco
- Вытащить результат работы racocoиз файла.racoco/report.txt. Нам нужна только первая строчка — процент покрытия. Убирается дробную часть и знак процента. Результат складываем во временную переменную окружения сборкиCOVERAGE;- name: Discover Code Coverage Level run: | coverage=head -1 .racoco/report.txt | sed 's/\.*//'echo "COVERAGE=$coverage%" >> $GITHUB_ENV shell: bash
- Для построения шилдика используем плагин schneegans/dynamic-badges-action;- name: Create Code Coverage Badge uses: schneegans/dynamic-badges-action@v1.1.0 with: auth: $\{\{ secrets.<YOUR_TOCKEN> }} gistID: <gist-ID> filename: <you-gist-file>.json label: Coverage message: $\{\{ env.COVERAGE }}5. Добавляем строчку со ссылкой на шилдик в README.md.
Пример почти такого YAML и итоговый шилдик можно посмотреть в репозитории App::RaCoCo.
English version