Bazel incluye un subcomando coverage
para generar informes de cobertura de código en repositorios que se pueden probar con bazel coverage
. Debido a las peculiaridades de los diversos ecosistemas de idiomas, no siempre es trivial hacer que esto funcione para un proyecto determinado.
En esta página, se documenta el proceso general para crear y ver informes de cobertura, y también se incluyen algunas notas específicas del lenguaje para aquellos lenguajes cuya configuración es bien conocida. Se recomienda leer primero la sección general y, luego, los requisitos para un idioma específico. También ten en cuenta la sección de ejecución remota, que requiere algunas consideraciones adicionales.
Si bien es posible realizar muchas personalizaciones, este documento se enfoca en la generación y el consumo de informes de lcov
, que actualmente es la ruta mejor admitida.
Cómo crear un informe de cobertura
Preparación
El flujo de trabajo básico para crear informes de cobertura requiere lo siguiente:
- Un repositorio básico con destinos de prueba
- Una cadena de herramientas con las herramientas de cobertura de código específicas del lenguaje instaladas
- Una configuración correcta de "instrumentación"
Los dos primeros son específicos del idioma y, en su mayoría, sencillos, pero el último puede ser más difícil para proyectos complejos.
En este caso, "instrumentación" se refiere a las herramientas de cobertura que se usan para un destino específico. Bazel permite activar esta opción para un subconjunto específico de archivos con la marca --instrumentation_filter
, que especifica un filtro para los destinos que se prueban con la instrumentación habilitada. Para habilitar la instrumentación de las pruebas, se requiere la marca --instrument_test_targets
.
De forma predeterminada, Bazel intenta hacer coincidir los paquetes de destino y, luego, imprime el filtro pertinente como un mensaje INFO
.
Cobertura de ejecución
Para generar un informe de cobertura, usa bazel coverage
--combined_report=lcov
[target]
. Esto ejecuta las pruebas para el destino y genera informes de cobertura en formato lcov para cada archivo.
Una vez que finaliza, bazel ejecuta una acción que recopila todos los archivos de cobertura producidos y los combina en uno solo, que luego se crea finalmente en $(bazel info
output_path)/_coverage/_coverage_report.dat
.
También se generan informes de cobertura si fallan las pruebas, aunque ten en cuenta que esto no se aplica a las pruebas fallidas, sino que solo se informan las pruebas aprobadas.
Visualización de la cobertura
El informe de cobertura solo se genera en el formato lcov
no legible. A partir de esto, podemos usar la utilidad genhtml
(parte del proyecto lcov) para generar un informe que se puede ver en un navegador web:
genhtml --output genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat"
Ten en cuenta que genhtml
también lee el código fuente para anotar la cobertura faltante en estos archivos. Para que esto funcione, se espera que genhtml
se ejecute en la raíz del proyecto de Bazel.
Para ver el resultado, simplemente abre el archivo index.html
que se produjo en el directorio genhtml
en cualquier navegador web.
Para obtener más ayuda e información sobre la herramienta genhtml
o el formato de cobertura lcov
, consulta el proyecto lcov.
Ejecución remota
Actualmente, la ejecución de pruebas remotas tiene algunas advertencias:
- La acción de combinación de informes aún no se puede ejecutar de forma remota. Esto se debe a que Bazel no considera los archivos de salida de la cobertura como parte de su gráfico (consulta este problema) y, por lo tanto, no puede tratarlos correctamente como entradas para la acción de combinación. Para solucionar este problema, usa
--strategy=CoverageReport=local
.- Nota: Es posible que debas especificar algo como
--strategy=CoverageReport=local,remote
en su lugar si Bazel está configurado para intentarlocal,remote
, debido a la forma en que Bazel resuelve las estrategias.
- Nota: Es posible que debas especificar algo como
--remote_download_minimal
y marcas similares tampoco se pueden usar como consecuencia de lo anterior.- Actualmente, Bazel no puede crear información de cobertura si las pruebas se almacenaron en caché anteriormente. Para solucionar este problema, se puede configurar
--nocache_test_results
específicamente para las ejecuciones de cobertura, aunque esto, por supuesto, genera un costo elevado en términos de tiempos de prueba. --experimental_split_coverage_postprocessing
y--experimental_fetch_all_coverage_outputs
- Por lo general, la cobertura se ejecuta como parte de la acción de prueba, por lo que, de forma predeterminada, no obtenemos toda la cobertura como resultados de la ejecución remota. Estas marcas anulan la configuración predeterminada y obtienen los datos de cobertura. Consulta este problema para obtener más detalles.
Configuración específica del idioma
Java
Java debería funcionar de inmediato con la configuración predeterminada. Las cadenas de herramientas de Bazel también contienen todo lo necesario para la ejecución remota, incluido JUnit.
Python
Requisitos previos
Ejecutar la cobertura con Python tiene algunos requisitos previos:
- Un binario de Bazel que incluye b01c859, que debería ser cualquier Bazel >3.0.
- Una versión modificada de coverage.py.
Cómo consumir el archivo coverage.py modificado
Una forma de hacerlo es a través de rules_python, que proporciona la capacidad de usar un archivo requirements.txt
. Los requisitos que se enumeran en el archivo se crean como destinos de Bazel con la regla de repositorio pip_install.
El requirements.txt
debe tener la siguiente entrada:
git+https://github.com/ulfjack/coveragepy.git@lcov-support
Luego, los archivos rules_python
, pip_install
y requirements.txt
se deben usar en el archivo WORKSPACE de la siguiente manera:
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "rules_python",
url = "https://github.com/bazelbuild/rules_python/releases/download/0.5.0/rules_python-0.5.0.tar.gz",
sha256 = "cd6730ed53a002c56ce4e2f396ba3b3be262fd7cb68339f0377a45e8227fe332",
)
load("@rules_python//python:pip.bzl", "pip_install")
pip_install(
name = "python_deps",
requirements = "//:requirements.txt",
)
Luego, los destinos de prueba pueden consumir el requisito de coverage.py si se establece lo siguiente en los archivos BUILD
:
load("@python_deps//:requirements.bzl", "entry_point")
alias(
name = "python_coverage_tools",
actual = entry_point("coverage"),
)
py_test(
name = "test",
srcs = ["test.py"],
env = {
"PYTHON_COVERAGE": "$(location :python_coverage_tools)",
},
deps = [
":main",
":python_coverage_tools",
],
)
Si usas una cadena de herramientas de Python hermética, en lugar de agregar la dependencia de cobertura a cada destino py_test
, puedes agregar la herramienta de cobertura a la configuración de la cadena de herramientas.
Dado que la regla pip_install depende de la cadena de herramientas de Python, no se puede usar para recuperar el módulo coverage
.
En su lugar, agrega tu WORKSPACE
, p.ej.,
http_archive(
name = "coverage_linux_x86_64"",
build_file_content = """
py_library(
name = "coverage",
srcs = ["coverage/__main__.py"],
data = glob(["coverage/*", "coverage/**/*.py"]),
visibility = ["//visibility:public"],
)
""",
sha256 = "84631e81dd053e8a0d4967cedab6db94345f1c36107c71698f746cb2636c63e3",
type = "zip",
urls = [
"https://files.pythonhosted.org/packages/74/0d/0f3c522312fd27c32e1abe2fb5c323b583a5c108daf2c26d6e8dfdd5a105/coverage-6.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
],
)
Luego, configura tu cadena de herramientas de Python, por ejemplo, de la siguiente manera:
py_runtime(
name = "py3_runtime_linux_x86_64",
coverage_tool = "@coverage_linux_x86_64//:coverage",
files = ["@python3_9_x86_64-unknown-linux-gnu//:files"],
interpreter = "@python3_9_x86_64-unknown-linux-gnu//:bin/python3",
python_version = "PY3",
)
py_runtime_pair(
name = "python_runtimes_linux_x86_64",
py2_runtime = None,
py3_runtime = ":py3_runtime_linux_x86_64",
)
toolchain(
name = "python_toolchain_linux_x86_64",
exec_compatible_with = [
"@platforms//os:linux",
"@platforms//cpu:x86_64",
],
toolchain = ":python_runtimes_linux_x86_64",
toolchain_type = "@bazel_tools//tools/python:toolchain_type",
)