Покрытие кода в Rust

Сначала поговорим об истории покрытия кода в 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.

Вы также можете использовать сам проект в качестве отправной точки, чтобы получить все эти преимущества еще до написания первой строчки кода.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Dotan Nahum: How to do code coverage in Rust

Предыдущая статьяКак профессионально использовать сопоставимые типы TypeScript
Следующая статьяАнимация границ с помощью свойств CSS