# Coverage

`ya make` позволяет запускать инструментированные тесты в режиме подсчёта покрытия, предоставляя HTML отчёты в удобном для чтения виде.
Так же для всех проектов в Акрадии еженощно считается покрытие, которое отображается в Acanum и CI.

## Поддерживаемые языки
* **С++**
Покрытие извлекается с помощью [clang’s source-based code coverage](http://bcain-llvm.readthedocs.io/projects/clang/en/latest/SourceBasedCodeCoverage/), который предоставляет покрытие с необходимой точностью, в частности конструируя сегменты для [макросов](https://a.yandex-team.ru/arc/trunk/arcadia/library/unittest/registar.h?coverage=true&rev=3435683#L287).
Для локального получения cpp покрытия и HTML отчёта, выполните `ya make -t --clang-coverage --coverage-report --output /tmp/report`. HTML отчёт будет находиться в директории `/tmp/report/coverage.report`.
* **Java / Kotlin**
Используется стандартный [jacoco](http://www.eclemma.org/jacoco/trunk/doc/report-mojo.html), который предоставляет построчное покрытие с указанием наличия [непокрытых трасс выполнения (бранчей) в строке](https://a.yandex-team.ru/arc/trunk/arcadia/devtools/dummy_arcadia/test_java_coverage/src/consoleecho/CrazyCalculator.java?coverage=true&rev=2902042#L28). Жёлтая полоска в UI Аrcanum указывает на наличие таких бранчей.
Для локального получения java покрытия и HTML отчёта, выполните `ya make -t --java-coverage --coverage-report --output /tmp/report`. HTML отчёт будет находиться в директории `/tmp/report/java.coverage.report`.
* **Python2 / Python3**
Для python используется модуль [coverage](https://pypi.python.org/pypi/coverage), который так же предоставляет построчное покрытие с поддержкой непокрытых трасс выполнения в строке.
Для локального получения python покрытия и HTML отчёта, выполните `ya make -t --python-coverage --coverage-report --output /tmp/report`. HTML отчёт для python2 кода будет находиться в директории `/tmp/report/py2.coverage.report`, а для python3, соответственно, в `/tmp/report/py3.coverage.report`.
* **Cython**
Используется плагин cython'a для python coverage, который предоставляет [построчное покрытие](https://a.yandex-team.ru/arc/trunk/arcadia/devtools/ya/yalibrary/store/hash_map/open_hash_map.pyx?rev=3635400&coverage=true) без поддержки бранчей. Покрытие cython'а считается подмножеством python покрытия и будет автоматически считаться при указании `--python-coverage` и попадать в HTML отчёты для python покрытия.
* **Go**
Для Go используется стандартная утилита [cover](https://blog.golang.org/cover), которая предоставляет покрытие сегментов кода в бинарном формате (покрыт/не покрыт). Сегментами называются трассы выполнения без условных операторов, сегмент всегда либо целиком покрыт, либо целиком не покрыт, из-за этого могут появляться артефакты, например, в случае, если в середине сегмента расположен оператор `return` или бросается ошибка.
Для локального получения go покрытия и HTML отчёта, выполните `ya make -t --go-coverage --coverage-report --output /tmp/report`, HTML отчёт будет находиться в директории `/tmp/report/go.coverage.report`.

## Полнота покрытия

Для расширения полноты покрытия, во время тестирования с подсчётом покрытия `ya make` так же собирает данные от всех дочерних процессов и загружаемых shared objects `so`. Важно, чтобы дочерние процессы запускались с передаваемыми в тест переменными окружения  `PYTHON_COVERAGE_PREFIX`, `LLVM_PROFILE_FILE`, `GO_COVERAGE_PREFIX`, `NLG_COVERAGE_FILENAME`.

Это, например, позволяет проводить интеграционное тестирование с использованием удобного pytest для cpp программ не теряя данных о покрытии для cpp кода.
Сейчас это поддерживается только для cpp и python программ. Если такая необходимость возникнет для go, опишите её в [DEVTOOLS-6838](https://st.yandex-team.ru/DEVTOOLS-6838).

Полнота покрытия так же достигается за счёт извлечения сегментов для целей `PROGRAM`, `PY2_PROGRAM`, `PY3_PROGRAM` и `BENCHMARK`, позволяя показывать отсутствующее покрытие для исходных кодов, которые не используются/ликнуются непосредственно в тестах.

## Комбинирование опций для получения покрытия
Все опции получения покрытия совместимы между собой и вы можете указывать их вместе. Так например, если вы хотите получить python covearge вместе с cpp покрытием, то после запуска команды `ya make -t --clang-coverage --python-coverage --coverage-report --output /tmp/report` будет собрано покрытие как для python-тестов и вызываемых в них python программ, так и для всех плюсовых библиотек, которые используются в pytest-тестах вместе с покрытием от всех cpp программ собранных из Аркадии и запускаемых в тесте.

## HTML отчёт
HTML отчёт генерируется в output директорию (`--output <path>`) если указан ключ `--coverage-report`. Так как в запуске может быть несколько разных ЯП, отчёт от каждого из них кладётся каждый в свою директорию - `coverage.report` (cpp), `java.coverage.report`, `py3.coverage.report`, etc. Для генерации отчёта используется стандартный туллинг.

### Фильтрация  

С помощью `--coverage-prefix-filter <arcadia-root-relative-path>` и `--coverage-exclude-regexp <regexp>` можно вырезать ненужные файлы из HTML отчёта. На текущий момент фильтрация поддержана для clang-coverage и java. Для cpp реализована оптимизация, которая не инструментирует компиляцию исходников, которые не попали под указанные фильтры, что может ускорить сборку и время работы тестов.
Если вам нужна фильтрация для других ЯП, напишите в [support](https://forms.yandex-team.ru/surveys/devtools/).

## Автоматизация получение покрытия
Еженощно TestEnv создаёт задачу, которая порождает 2 типа задач: одни для запуска всех small и medium на кластере распределенной сборки Distbuild, и дополнительно по отдельной таске на каждый `LARGE` тест размеченный тегом `ya:sandbox_coverage`.
`LARGE` тесты по умолчанию не добавляются в еженощный прогон. Если вам нужно покрытие от таких тестов, их следует явно разметить тегам `ya:force_sandbox ya:sandbox_coverage`.
Покрытие считается для следующих целей: `UNITTEST`, `PY2TEST`, `PY3TEST`, `PY23_TEST` , `JTEST`, `EXECTEST`, `GO_TEST_FOR`, `GTEST`, `BOOSTTEST`, достижимых от корня Аркадии по `RECURSE`.
Тесты запускаются с увеличенным в [полтора раза](https://a.yandex-team.ru/arc/trunk/arcadia/devtools/ya/test/const/__init__.py?rev=3612108#L36) таймаутом.

По мере прохождения тестов мы сводим покрытие от всех запущенных бинарей и so используемых в тестах в унифицированный формат и заливаем его в YT, где потом мёржим данные от `всех` тестов, которые публикуем в CI и Arcanum для указанной ревизии.

## Отображение покрытия в Arcanum
### Колоризация покрытых/не покрытых участков в файле
Данные полученные от всех тестов сливаются для каждого файла и доступны для просмотра в Arcanum. Цвет полоски рядом с номером строки указывает на имеющиеся данные о покрытии для этой строки.
![example](https://jing.yandex-team.ru/files/prettyboy/Screenshot_2020-07-31_11-30-46.png)
Зелёная - все сегменты в строке покрыты.
Жёлтая - в строке есть покрытие и не покрытие сегменты (для сpp) или есть не покрытый бранч (для остальных ЯП).
Красная - не покрытые участки кода.

Для файлов имеющих данные о покрытии доступна кнопка Coverage в шапке меню файла:
![header](https://jing.yandex-team.ru/files/prettyboy/1234-2.png)
Отображаются доступные данные для ревизии ближайшей к просматриваемой с учётом совпадения контента файла.

Например, если для ревизии 10 было посчитано покрытие для file.cpp, текущий head trunk'a - 15 и содержимое file.cpp не менялось, то при колоризации будут отображаться данные посчитанные на ревизии 10. Если file.cpp был изменён в 16 ревизии, то данные для него недоступны вплоть до следующего еженощного пересчёта. Однако, можно посмотреть `Outdated Coverage` или открыть историю переключиться на версия файла до 16 ревизии, где будут доступны данные о покрытии.

### Суммарный coverage в Directory View
Покрытие каталога считается как среднее от агрегированного покрытия всех входящих в него и вложенных файлов. При просмотре dirview, с включённым coverage, отображаются ближайшие (слева) к просматриваемой ревизии данные. В скобках указывается количество файлов для которых есть покрытие в текущей и нижележащих директориях.
![dir view](https://jing.yandex-team.ru/files/prettyboy/Screenshot_2020-07-31_13-04-45.png)
Для перемещённых файлов/каталогов эти данные недоступны до следующего еженощного пересчёта. Удаление/перемещение/изменение файлов/каталогов не меняет и не пересчитывает отображаемое покрытие - оно относится исключительно к моменту (ревизии) его получения.

## Отображение покрытия в CI
Результаты сборки программ и прогона тестов доступны в CI в виде тулчейна `default-linux-x86_64-release-coverage`.

{% note warning %}

Данные для тулчейна `default-linux-x86_64-release-coverage` пока временно не отправляются. Починим в рамках [DEVTOOLS-5023](https://st.yandex-team.ru/DEVTOOLS-5023)

{% endnote %}

Для отображения покрытия в CI необходимо отредактировать созданный проект и [добавить widget Coverage](https://clubs.at.yandex-team.ru/arcadia/11825).
На графике будет отображаться агрегированное покрытие для каждого языка программирования с применением фильтрации по путям, на основе которых создан проект. Учитываются все файлы для которых есть покрытие без исключений, включая тесты. Нажав на интересующую ревизию, можно посмотреть детальное покрытие по файлам.
Агрегированный coverage считается как среднее от `lines`, `segments` и `functions`, без учёта `instantiations`.
![ci example](https://jing.yandex-team.ru/files/prettyboy/Screenshot_2020-07-31_11-44-50.png)

### Почему покрытие прыгает на графиках?
1. Таймауты и ошибки в тестах
2. Таймауты при резолве покрытия
3. [TESTENV-2197](https://st.yandex-team.ru/TESTENV-2197)

## Причины отсутствия покрытия в CI или Arcanum
1. Проект не подключен к автосборке - следует проверить наличие теста/программы в https://ci.yandex-team.ru/tests.
2. Код не используется в тестах.
3. Программа/тест не собирается. Проверить что есть успешная сборка и запуск теста в https://ci.yandex-team.ru/tests для тулчейна `default-linux-x86_64-release-coverage` (тулчейн временно отключен, см. [DEVTOOLS-5023](https://st.yandex-team.ru/DEVTOOLS-5023))
4. Тест таймаутится, оставшаяся часть тестов не выполнилась, из-за этого может не быть части покрытия.
5. Вы завершаете процесс теста или дочерний процесс с помощью терминирующего сигнала (`SIGKILL`, etc) в конце тестирования или вызываете `abort()`/`terminate()` - данные о покрытии дампятся только при корректном завершении программы.
6. В методе `stop()` рецепта вы отправляете сигнал процессу и не дожидаясь его завершения выходите из метода.
7. Процесс должен запускаться с передаваемыми в тест переменными окружения `LLVM_PROFILE_FILE`, `GO_COVERAGE_PREFIX`, `PYTHON_COVERAGE_PREFIX`, `NLG_COVERAGE_FILENAME`, иначе он не сдампит покрытие.
8. Тест имеет размер `LARGE` с выставленным тегом `ya:force_sandbox`. Такие тесты по-умолчанию не запускаются с подсчётом покрытия - нужно явно добавить тег `ya:sandbox_coverage`.
9. Для java-тестов, тест не запускается если класс помечен `@YaIgnore`.
10. `#ifdef` внутри тела функции считается непокрытым сегментом [DEVTOOLS-4200](https://st.yandex-team.ru/DEVTOOLS-4200).
11. Для Go-тестов, мы не можем получить покрытие исходников, расположенных не в директории с тестом.
12. В pytest-тесте вы переопределяете обработчик сигнала для `SIGUSR2`, что в случае таймаута теста может приводить к отсутствию данных о покрытии.
13. `NO_CYTHON_COVERAGE` отключает инструментирование не только для ситонизированных python исходников, но и всего плюсового кода, подключаемого напрямую в модуле (через `SRCS`). Если вам это нужно сообщите об этом в [DEVTOOLS-6668](https://st.yandex-team.ru/DEVTOOLS-6668)
14. Вы используете `docker_compose` без настройки, см. ниже.

#### Настройка docker_compose
Если целевая программа запускается в `docker_compose`, вам нужно дополнительно настроить окружение в yaml описании:
- Добавить передачу переменных окружения `LLVM_PROFILE_FILE`, `GO_COVERAGE_PREFIX`, `PYTHON_COVERAGE_PREFIX` в секцию `environment` docker конфигурации.
- Добавить монтирование директорий для данных с покрытием с помощью секции `volumes`:
    ```js    
    volumes:
      - ${GO_COVERAGE_DIRNAME:-/dev/null}:${GO_COVERAGE_DIRNAME:-/go_coverage_volume}
      - ${LLVM_PROFILE_DIRNAME:-/dev/null}:${LLVM_PROFILE_DIRNAME:-/cpp_coverage_volume}
      - ${PYTHON_COVERAGE_DIRNAME:-/dev/null}:${PYTHON_COVERAGE_DIRNAME:-/python_coverage_volume}
    ```
- Для указанных volume нужно выставить права доступа `777`, в противном случае не-root пользователи не смогут сохранить coverage данные в примонтированные volume. Это можно сделать на этапе подготовке окружения с помощью скрипта:
    ``` bash
    #!/bin/sh -ex
    if [ -n "$GO_COVERAGE_DIRNAME" ]; then
        chmod 777 $GO_COVERAGE_DIRNAME
    fi
    if [ -n "$LLVM_PROFILE_DIRNAME" ]; then
        chmod 777 $LLVM_PROFILE_DIRNAME
    fi
    if [ -n "$PYTHON_COVERAGE_DIRNAME" ]; then
        chmod 777 $PYTHON_COVERAGE_DIRNAME
    fi
    ```
- Увеличить graceful shutown таймаут задав параметр `stop_grace_period: 1m30s`. По-умолчанию при остановке теста `docker-compose` шлёт `SIGTERM`, ждёт 10 секунд и отправляет `SIGKILL`, что может привести к тому что данные не успеют корректно сохраниться.

## Отключение Coverage
* для отключения конкретного теста, достаточно написать в ya.make
```
IF(CLANG_COVERAGE)
    TAG(ya:not_autocheck)
ENDIF()
```
* для отключения инструментирования библиотеки или любой другой цели нужно добавить  `NO_CLANG_COVERAGE()`

## График сходимости еженощного покрытия
[График](https://solomon.yandex-team.ru/?project=coverage&service=nightly&cluster=arcadia&l.sensor=delay_hours&graph=auto&b=1w&e=) показывает отстование подсчёта еженощного покрытия. Мы сделим за ним и в случае роста отстования занимаемся починкой.

## Покрытие в review
В прекоммитной проверке можно получать изменения покрытия определенной ветки кода с выбранным набором тестов. Это может быть удобно, если необходимо отслеживать покрытие кода только конкретными юнит-тестами, которые для него написаны.
[Подробности](https://clubs.at.yandex-team.ru/arcadia/20533).

## Maintenance
Внутреняя документация для ручной заливки данных: https://wiki.yandex-team.ru/yatool/coverage/maintenance/

## Alerts and dashboards
[Coverage relevance](https://solomon.yandex-team.ru/admin/projects/coverage/alerts).

[Coverage distbuild dashboard](https://solomon.yandex-team.ru/?project=coverage&dashboard=nightly_coverage_distbuild)

[Coverage sandbox dashboard](https://solomon.yandex-team.ru/?project=coverage&dashboard=nightly_coverage_sandbox)

[Coverage meta dashboard](https://solomon.yandex-team.ru/?project=coverage&dashboard=nightly_coverage_meta)
