Arreglar los reportes de cobertura de pruebas en at-spi2-core

Translations: en - Tags: accessibility, gnome

Durante las últimas semanas he estado arreglando el reporte de cobertura de pruebas en at-spi2-core; una aventura donde tuve que hacer esto:

  • Remplazar una herramienta de cobertura de código por otra...
  • ... que iba a ser más fácil de arreglar para que produjera reportes HTML accesibles.
  • Averiguar por qué algunos módulos de at-spi2-core's salían con 0% de cobertura.
  • Aprender a hacer dobles de prueba (mocks) para servicios DBus.

¿Qué es un reporte de cobertura de código?

En breve — corres tu programa, o su conjunto de pruebas. Generas un reporte de cobertura, el cual te dice qué lineas de código se ejecutaron y cuáles no.

¡Un reporte de cobertura es muy útil! Te permite responder algunas preguntas a gran escala:

  • ¿Qué código de mi proyecto no se ejecuta durante las pruebas?

  • Si hay código compilado condicionalmente y que depende de opciones que se ponen en tiempo de compilación, ¿estoy olvidando probar una configuración en especial?

Y también preguntas a pequeña escala:

  • La prueba que acabo de añadir, ¿de verdad hace que se ejecute el código que quiero probar?

  • ¿Hay pruebas para todo el código de manejo de errores?

También puedes usar un reporte de cobertura como una herramienta para explorar:

  • Corre el programa a mano y haz algunas cosas con él. ¿Qué código se ejecutó a través de tus acciones?

Quiero poder hacer todas esas cosas con la infraestructura de accesibilidad: usar el reporte como una herramienta de exploración mientras aprendo cómo funciona el código, y usarlo como herramienta para asegurarme que las pruebas que añada de veras prueban el código que deben probar.

Un pedacito de un reporte de cobertura

Este es un pantallazo del reporte para at-spi2-core/atspi/atspi-accessible.c:

Reporte de cobertura para la función atspi_accessible_get_child_count()

La columna de la izquierda es el número de línea en el código fuente. La segunda columna tiene el número de veces que se ejecutó esa línea: las líneas en verde se ejecutaron una o más veces; las líneas en rojo no se ejecutaron; las líneas en blanco no son ejecutables.

Al observar ese pedacito del reporte, podemos comenzar a hacer preguntas:

  • Hay un return -1 para una condición de error, que no se ejecuta. ¿El código que llama a éste, sí sabría manejar esa condición correctamente, ya que no tenemos pruebas para ello?

  • Las últimas líneas de la función no se ejecutan, pues justo antes hay una condición que prueba un cache. ¿Cómo podemos probar esas líneas y causar que se ejecuten? ¿Son necesarias, o el cache debería cachar todo? ¿Cómo podemos probar un comportamiento diferente en el cache?

Primera iteración: lcov

Cuando le puse infraestructura de integración contínua (IC) a at-spi2-core, copié casi todo de la IC de libgweather, pues Emmanuele Bassi le había puesto cosas lindas como análisis estático de código, address-sanitizer, y un reporte de cobertura generado con lcov.

Los primeros reportes de lcov pintaban un panorama algo deprimente: sólo había cobertura de pruebas para un 30% del código. Sin embargo, algunos módulos que definitivamente se ejecutan durante las pruebas salían con 0% de cobertura. Eso estaba mal; esos módulos definitivamente tienen código que se ejecuta; ¿por qué no sale en el reporte?

Cobertura cero

At-spi2-core tiene algunos módulos un tanto peculiares. No contiene sólo un programa o biblioteca que se ejecuta una vez, sino que tiene varias bibliotecas y demonios que se usan a través de esas bibliotecas, o a través de llamadas vía DBus.

En particular, at-spi2-registryd es el demonio que mantiene el registro de aplicaciones accesibles y que las multiplexa hacia las herramientas de accesibilidad como los lectores de pantalla. Este demonio no usa el bus de la sesión; se registra en un dbus-daemon separado y de uso exclusivo para accesibilidad, para no saturar de tráfico al bus principal de la sesión del usuario.

at-spi2-registryd arranca en cuanto algo requiere de las APIs de accesibilidad, y sigue corriendo hasta que termina la sesión del usuario.

Sin embargo, durante la suite de pruebas, no hay sesión del usuario. El demonio corre, y su dbus-daemon padre le manda un SIGTERM al terminar. Entonces, aunque at-spi2-registryd no tiene estado persistente que debería guardar antes de salir, no se termina "de manera limpia".

Y resulta que los datos de cobertura de gcc sólo se almacenan si el programa termina de manera limpia. Cuando compilas con la opción --coverage, gcc emite código que enciende la bandera en libgcc para que se escriban los datos de cobertura al terminar el programa (libgcc es parte de lo que se enlaza en cualquier programa compilado con gcc, y tiene código auxiliar como ése, para escribir la info de cobertura).

Es como si main() tuviera una envoltura:

void envoltura_para_main(void)
{
    int r = main(argc, argv);

    escribir_informacion_de_cobertura();

    exit(r);
}

int main(int argc, char **argv) 
{
    /* tu programa va aquí */
}

Desde luego, si el programa termina de forma prematura vía SIGTERM, la envoltura no va a terminar de ejecutarse y no va a poder escribir la información de cobertura.

Entonces, ¿cómo simulamos una sesión de ususario durante las pruebas?

Un doble de pruebas (mock) para gnome-session

Hace poco me enteré de una herramienta fantástica, python-dbusmock, que hace muy fácil escribir dobles de prueba para interfases de DBus.

Algunas partes de at-spi2-core dependen de poder monitorear el tiempo de vida de la sesión del usuario. Por fortuna, sólo requieren de dos cosas de las interfases de gnome-session:

Escribí un doble de pruebas para esas interfases de DBus para que los demonios se registren ante un manejador de sesiones de mentiritas. Luego, hice que el ejecutor de pruebas le diga a la sesión de mentiritas que ya se terminó, para que les pueda avisar a los demonios que tienen que terminar su ejecución cuando finalizan las pruebas.

Con eso, at-spi2-registryd ya tiene información de cobertura de código como debe ser.

Obtener cobertura para atk-adaptor

atk-adaptor es un montón de código que funciona como pegamento entre atk, la biblioteca basada en GObject y que se usa en GTK3 para accesibilidad, y libatspi, el pegamento escrito a mano para comunicarse vía DBus con las interfases de accesibilidad.

Las pruebas para atk-adaptor son muy interesantes. Tienen que simular una aplicación que usa atk para volverse accesible, y quieren probar, por ejemplo, que un lector de pantalla sí se pueda comunicar con la aplicación. Y en lugar de escribir implementaciones de ATK a mano, las pruebas tienen descripciones en XML de los objetos que deberían simularse con ATK, y algo de código auxiliar para crear esos objetos a partir del XML. Cada prueba individual usa una descripción XML diferente, y lanza un programita auxiliar para leer ese XML.

Ahora bien, resulta que el ejecutor de pruebas sólo le manda un SIGTERM a ese programita auxiliar cuando cada prueba individual termina. Eso está bien para correr las pruebas de forma normal, pero impide que se escriba la información de cobertura cuando el programita auxiliar termina.

Entonces, puse un manejador de señales de gmain en el programita auxiliar, para que terminara de manera limpia cuando le llega un SIGTERM. ¡Problema resuelto!

Falta cobertura para GTK2

La única parte de at-spi2-core que todavía no guarda información de cobertura es el código que funciona como pegamento para GTK2. Creo que sería necesario ejecutar un programa de pruebas (que todavía no existe) bajo xvfb, para que si libgtk2 pueda lanzar el módulo de pegamento de accesibilidad. No estoy seguro si eso debería probarse desde at-spi2-core, o si debería ser responsabilidad de GTK2.

¿Los reportes de cobertura son accesibles?

Para un persona con vista, es fácil ver un reporte de cobertura como el del pantallazo de arriba y buscar las líneas en rojo — esas son las que no se ejecutaron.

Para gente que usa lectores de pantalla, no es tan conveniente. Pregunté por ahí, y Eitan Isaacson me dio consejos muy buenos sobre cómo mejorar la accesibilidad de los reportes HTML de lcov y grcov.

Lcov es una herramienta vieja, que había empezado a usar para at-spi2-core porque es la misma que usa libgweather en su IC. Grcov es una herramienta más nueva, escrita por gente de Mozilla, y que la usan para los reportes de cobertura de Firefox. Grcov también es la herramienta que usamos para librsvg. Como prefiero atender sólo una herramienta en vez de dos, decidí cambiar at-spi2-core para que también usara grcov, y mejorarle la accesibilidad a los reportes HTML.

El pedazo del pantallazo de arriba parece una tabla con tres columnas (número de línea, número de veces que se ejecutó, código fuente), pero no es una <table> de verdad. Está hecha con elementos div y estilos. Algo así:

<div>
  <div class="columns">
    <div class="column">
      número de línea
    </div>
    <div class="column color-coding-executed">
      número de veces que se ejecutó
    </div>
    <div class="column color-coding-executed">
      <pre>código fuente</pre>
    </div>
  </div>
  <!-- lo de arriba se repite para cada línea de código -->
</div>

Eitan me mostró cómo usar las etiquetas de ARIA para que esos divs se expongan como algo que sí se puede navegar como una tabla:

  • Añadir role="table" aria-label="Reporte de cobertura" al <div> principal. Esto les dice a los navegadores de web que deben activar el modo de interacción de accesibilidad que usan para información en tablas. También le pone una etiqueta a la tabla, para quea fácil de encontrar por los usuarios de lectores de pantalla; por ejemplo, estos últimos permiten navegar a la siguente tabla en un documento, y sería bueno poder saber a qué se refiere la tabla, con la etiqueta.

  • Añadir role="row" a cada fila hecha con un div.

  • Añadir role="cell" a cada casilla hecha con un div.

  • Añadir un aria-label a las casillas con el número de veces que se ejecutó cada línea. Mientras que la versión para personas con vista no muestra nada para las líneas no ejecutables, o sólo un fondo rojo para las líneas que no se ejecutaron, o un número con fondo verde para las líneas ejecutadas, la versión para lectores de pantalla no puede depender sólo de un fondo coloreado. El aria-label contiene "sin cobertura", o "0", o el número de veces que se ejecutó la línea, respectivamente.

Con el tiempo sabremos si esto en efecto hace los reportes más fáciles de usar. Me preocupaba que fueran fáciles de revisar rápidamente, para encontrar las líneas que no se ejecutaron. Al usar los comandos del lector de pantalla para navegar las casillas de una tabla, uno puede moverse hacia abajo en la segunda columna hasta encontrar un "cero". ¿Tal vez haya una manera más rápida? ¡Se aceptan consejos!

Grcov ahora incluye ese parche.

Qué sigue

Estoy empezando a limpiar las interfases XML en at-spi2-core, al menos en cuanto a cómo se incluyen en la compilación. Espero tener noticias pronto.