Сначала поговорим об истории покрытия кода в Rust. Эта информация может пригодиться, если вы будете искать актуальные решения в Google.
История покрытия кода в Rust
Tarpaulin
Tarpaulin — это простой инструмент для реализации покрытия кода в Rust, но у него есть ограничения. Вот интуитивное описание работы Tarpaulin: он будет инструментировать код, а затем использовать ptrace
для прослушивания происходящего, чтобы подсчитать строки для анализа покрытия.
Именно поэтому Tarpaulin поддерживает только Linux, а также только процессоры x86_64.
Tarpaulin обеспечивает покрытие строк и является достаточно надежным инструментом, но все же выдает некоторые неточности в результатах.
gcov
До появления покрытия на основе исходного кода Rust использовал технику gcov
, которая полагалась на отладочную информацию, чтобы сопоставить LLVM IR (находится внизу, сгенерировано и скрыто от вас) со строками исходного кода (находятся наверху — это то, что вы привыкли видеть). Такая технология должна натолкнуть вас на мысль об именах в строках кода и о ведении подсчета того, какая строка выполняется и сколько раз.
Поскольку при переходе от исходного кода к скомпилированной двоичной форме разрешение данных теряется от слоя к слою, технология, основанная на gcov
, не является самой точной.
Покрытие на основе исходного кода
Покрытие на основе исходного кода позволяет Rust проводить инструментирование прямо на уровне компилятора, генерируя IR из исходного кода и передавая его затем на все этапы компиляции. После этого инструменты анализа покрытия используют всю эту высококачественную информацию для корреляции и точного соотнесения данной низкоуровневой конструкции и ее высокоуровневого исходного кода (который вы написали).
В результате достигается гораздо более высокий уровень точности и лучшая реализация инструментария покрытия. Чтобы понять, что такое “точность”, взгляните на это оператор короткой схемы:
if a < b && c > d {
// ..
}
Если a > b
(обратное первому условие), мы знаем, что следующее условие выполнять не нужно. Это и есть короткая схема. Покрытие на основе исходного текста сможет подсказать вам, что c > d
не выполнялось, поэтому вы можете написать тест и для этого условия, причем метод gcov
перечислит все строки как выполненные.
Сегодня покрытие на основе исходного кода в Rust является рекомендуемым методом. Это также самый удобный, точный и переносимый способ.
Цели
С помощью покрытия на основе исходного кода можно достичь трех целей.
- Выполнение на локальном уровне, чтобы обеспечить быструю обратную связь.
- Бесшовная интеграция в IDE.
- Запуск в CI, PR и централизованно для команды.
Выполнение на локальном уровне
Чтобы выполнить покрытие кода с помощью новых функций покрытия на основе исходного кода, необходимы определенные действия. Сначала рассмотрим шаги, выполняемые вручную, а потом перейдем к автоматизации.
Как правило, тесты помогают запустить покрытие, поэтому проведем cargo test
. Не забудем включить инструментирование, а также указать LLVM выводить необработанные данные профилирования:
$ CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' cargo test
Далее нужно взять все эти данные и сформировать отчет, который понимают инструменты и могут прочитать машины, а также тот, что мы сами можем просматривать.
В виде HTML:
$ grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
В виде lcov
, популярного формата вывода:
$ grcov . --binary-path ./target/debug/deps/ -s . -t lcov --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/tests.lcov
Затем мы сами решаем, что делать с данными.
- Указать IDE найти
lcov
-файлы для подсветки встроенного покрытия кода. - Загрузить
lcov
-файлы в провайдеры покрытия кода, такие как Codecov. - Использовать данные
lcov
для создания пользовательских отчетов или для провала тестов.
После завершения работы нужно очистить файлы *.profraw
, которые распространились по коду.
Автоматизация с помощью xtask
cargo-xtask
— это текущая реализация задач cargo. Можно сказать, что это альтернатива make
на основе Rust, но это чистый Rust. Ее преимущество — не в обилии строгих правил, а в использовании передовых технологий. Чтобы узнать больше, загляните в репозиторий xtask.
Вот как выглядит весь этот процесс в текущем варианте cargo xtask cover
(полная версия примера в rust-starter).
Если вы теперь выполните следующее:
$ cargo xtask coverage
В разделе coverage/
вас будут ждать файлы lcov
, которые можно загрузить в провайдер покрытия или использовать в других инструментах.
А если вы запустите:
$ cargo xtask coverage --dev
Вам будет предложено открыть отчет, который можно загрузить в браузер (не потребуется никакого другого продукта и провайдера).
Встроенное покрытие с VSCode
Если вы хотите видеть покрытие прямо в коде по мере ввода, сделать это можно с помощью расширения coverage gutters, которое рекомендуется использовать с любым языком, способным создавать lcov
, так что оно полезно не только для Rust.
После установки расширения нужно указать, где в проекте находятся файлы lcov
.
В данном случае, если вы используете xtask
для всех проектов Rust, они всегда будут находиться в coverage/tests.lcov
.
У меня вот такая конфигурация (привожу в качестве примера):
"coverage-gutters.coverageFileNames": [
"coverage/tests.lcov",
"lcov.info",
"cov.xml",
"coverage.xml",
"jacoco.xml",
"coverage.cobertura.xml"
],
Затем вы можете просмотреть встроенное покрытие с помощью опции watch
или просто выполнить переключение:
Запуск в CI
Далее нам нужно использовать ту же задачу покрытия в CI, и в этом случае снова проявляется еще одно преимущество xtask
. Выполнение покрытия в CI будет сводиться к следующему:
$ cargo xtask coverage
Затем нужно найти способ загрузить coverage/*.lcov
в провайдер покрытия.
В этом примере мы будем использовать Github Action в качестве CI, а Codecov в качестве провайдера. Действия будут почти аналогичными и у других поставщиков.
Рабочий процесс на Github также должен включать настройку необходимых инструментов покрытия, поэтому сначала установим llvm-tools-preview
:
- name: Install toolchain
id: toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-musl
override: true
components: llvm-tools-preview
Далее загружаем grcov
, выполняем остальную часть задачи и производим загрузку в Codecov:
- name: Download grcov
run: |
mkdir -p "${HOME}/.local/bin"
curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.10/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "${HOME}/.local/bin"
echo "$HOME/.local/bin" >> $GITHUB_PATH- name: Run xtask coverage
uses: actions-rs/cargo@v1
with:
command: xtask
args: coverage- name: Upload to codecov.io
uses: codecov/codecov-action@v3
with:
files: coverage/*.lcov
Вот и все. На этом этапе у вас должны быть:
- Инструменты локального покрытия и задача для быстрой обратной связи.
- Гладкая и бесшовная интеграция с IDE.
- Интеграция CI с хорошим провайдером покрытия кода для работы в команде и автоматического покрытия PR.
Полную версию рабочего процесса можно найти в проекте rust-starter.
Вы также можете использовать сам проект в качестве отправной точки, чтобы получить все эти преимущества еще до написания первой строчки кода.
Читайте также:
- Rust: взгляд старого программиста
- Почему я перехожу с Python на Rust
- Rust как часть микросервисной архитектуры
Читайте нас в Telegram, VK и Дзен
Перевод статьи Dotan Nahum: How to do code coverage in Rust