Cobertura de código con Bazel

Bazel cuenta con un subcomando coverage para producir informes de cobertura de código en repositorios que se pueden probar con bazel coverage. Debido a las idiosincrasias de los diferentes ecosistemas de lenguajes, 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 en los idiomas cuya configuración es conocida. La mejor opción es leer la sección general y, luego, leer los requisitos para un idioma específico. Observa también la sección de ejecución remota, que requiere algunas consideraciones adicionales.

Si bien se puede personalizar mucho, este documento se enfoca en la producción y el consumo de informes lcov, que es la ruta más admitida actualmente.

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 de instrumentación adecuada

Los dos primeros son específicos del lenguaje y son bastante directos, pero el segundo puede ser más difícil para los proyectos complejos.

En este caso, "Instrumentación" se refiere a las herramientas de cobertura que se usan para un objetivo específico. Bazel permite activar esta función en un subconjunto específico de archivos mediante la marca --instrumentation_filter, que especifica un filtro para los objetivos que se prueban con la instrumentación habilitada. Para habilitar la instrumentación de pruebas, se requiere la marca --instrument_test_targets.

De forma predeterminada, Bazel intenta hacer coincidir los paquetes de destino y, luego, imprime el filtro relevante como un mensaje INFO.

Cobertura activa

Para generar un informe de cobertura, usa bazel coverage --combined_report=lcov [target]. De esta manera, se ejecutan las pruebas del destino, lo que genera informes de cobertura en formato lcov para cada archivo.

Cuando finaliza, Bazel ejecuta una acción que recopila todos los archivos de cobertura generados y los combina en uno que, por último, se crea en $(bazel info output_path)/_coverage/_coverage_report.dat.

Los informes de cobertura también se generan si fallan las pruebas, aunque ten en cuenta que esto no se extiende a las pruebas con errores, solo se informan las que pasan.

Visualización de cobertura

El informe de cobertura solo se muestra en el formato lcov no legible. Desde allí, podemos usar la utilidad genhtml (parte del proyecto lcov) para producir un informe que se pueda 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, solo debes abrir el archivo index.html producido en el directorio genhtml en cualquier navegador web.

Para obtener más información y ayuda 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 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 solucionarlo, usa --strategy=CoverageReport=local.
    • Nota: Es posible que sea necesario especificar algo como --strategy=CoverageReport=local,remote, si Bazel está configurado para probar local,remote, debido a cómo Bazel resuelve estrategias.
  • --remote_download_minimal y otras marcas similares tampoco se pueden usar como consecuencia de la primera.
  • Actualmente, Bazel no podrá crear información de cobertura si las pruebas se almacenaron en caché con anterioridad. A fin de solucionar este problema, --nocache_test_results se puede configurar específicamente para ejecuciones de cobertura, aunque esto, por supuesto, genera un gran costo 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 el valor predeterminado y obtienen los datos de cobertura. Consulta este problema para obtener más información.

Configuración específica del lenguaje

Java

Java debería funcionar de inmediato con la configuración predeterminada. Las cadenas de herramientas de bazel contienen todo lo necesario para la ejecución remota, incluido JUnit.

Python

Requisitos previos

Ejecutar la cobertura con Python tiene los siguientes requisitos previos:

Cómo consumir la cobertura modificada

Una forma de hacerlo es mediante rules_python, que permite usar un archivo requirements.txt. Luego, los requisitos que se enumeran en el archivo se crean como objetivos de bazel con la regla de repositorio pip_install.

requirements.txt debe tener la siguiente entrada:

git+https://github.com/ulfjack/coveragepy.git@lcov-support

El archivo rules_python, pip_install y requirements.txt debe usarse 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 objetivos de prueba pueden consumir el requisito deCobertura.py si configuras 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",
    ],
)