Enciclopedia de prueba

Informa un problema Ver código fuente

Una especificación exhaustiva del entorno de ejecución de pruebas.

Información general

El lenguaje BUILD de Bazel incluye reglas que se pueden usar para definir programas de prueba automatizados en muchos lenguajes.

Las pruebas se ejecutan con bazel test.

Los usuarios también pueden ejecutar objetos binarios de prueba directamente. Esto está permitido, pero no se recomienda, ya que esa invocación no cumplirá con los mandatos que se describen a continuación.

Las pruebas deben ser herméticas, es decir, deben acceder solo a aquellos recursos en los que tienen una dependencia declarada. Si las pruebas no son herméticas de forma correcta, no generan resultados históricamente reproducibles. Este podría ser un problema importante para encontrar el culpable (determinar qué cambio rompió una prueba), la auditabilidad de la ingeniería de lanzamiento y el aislamiento de recursos de las pruebas (los frameworks de prueba automatizados no deberían ejecutar un servidor DSD porque algunas pruebas lo afectan).

Objetivo

El objetivo de esta página es establecer formalmente el entorno de ejecución para el comportamiento esperado de las pruebas de Bazel. También impone requisitos al ejecutor de pruebas y al sistema de compilación.

La especificación del entorno de pruebas ayuda a los autores de pruebas a evitar depender de un comportamiento no especificado y, por lo tanto, brinda a la infraestructura de pruebas más libertad para realizar cambios de implementación. La especificación ajusta algunos agujeros que actualmente permiten que se pasen muchas pruebas a pesar de no ser herméticos, deterministas y de reentrantes.

Esta página debe ser autoritaria y confiable. Si esta especificación y el comportamiento implementado del ejecutor de pruebas están en desacuerdo, tendrá prioridad.

Especificación propuesta

Las palabras clave "DEBE", "NO DEBE", "OBLIGATORIO", "DEBERÁ", "NO DEBERÍA", "DEBERÍA", "NO DEBERÍA", "RECOMENDADO", "PUEDE" y "OPCIONAL" se deben interpretar como se describe en IETF RFC 2119.

Propósito de las pruebas

El objetivo de las pruebas de Bazel es confirmar algunas propiedades de los archivos de origen registrados en el repositorio. (en esta página, "archivos de origen" incluye datos de prueba, resultados dorados y cualquier otro que se mantenga bajo control de versión). Un usuario escribe una prueba para confirmar una variante que espera mantener. Otros usuarios ejecutan la prueba más tarde para verificar si se produjo un error en la variante invariable. Si la prueba depende de variables que no sean archivos de origen (no herméticos), su valor disminuye porque los usuarios posteriores no pueden estar seguros de que sus cambios son incorrectos cuando la prueba deja de pasar.

Por lo tanto, el resultado de una prueba solo debe depender de lo siguiente:

  • archivos de origen en los que la prueba tiene una dependencia declarada
  • productos del sistema de compilación en el que la prueba tiene una dependencia declarada
  • recursos cuyo comportamiento está garantizado por el ejecutor de pruebas

Actualmente, ese comportamiento no se aplica de manera forzosa. Sin embargo, los ejecutores de pruebas se reservan el derecho de agregar la aplicación forzosa en el futuro.

Función del sistema de compilación

Las reglas de prueba son análogas a las reglas binarias, ya que cada una debe producir un programa ejecutable. Para algunos lenguajes, este es un programa de stub que combina un agente de prueba específico de idioma con el código de prueba. Las reglas de prueba también deben producir otros resultados. Además del ejecutable de prueba principal, el ejecutor de pruebas necesitará un manifiesto de runfiles, archivos de entrada que deben estar disponibles para la prueba en el tiempo de ejecución, y puede necesitar información sobre el tipo, el tamaño y las etiquetas de una prueba.

El sistema de compilación puede usar los runfiles para entregar código y datos. (puede usarse como optimización para reducir el tamaño de cada objeto binario de prueba mediante el uso compartido de archivos entre pruebas, por ejemplo, mediante el uso de vínculos dinámicos). El sistema de compilación debe asegurarse de que el ejecutable generado cargue estos archivos mediante la imagen de archivos de ejecución que proporciona el ejecutor de pruebas, en lugar de referencias codificadas a ubicaciones absolutas en el árbol de fuentes o de resultados.

Función del ejecutor de pruebas

Desde el punto de vista del ejecutor de pruebas, cada prueba es un programa que se puede invocar con execve(). Puede haber otras formas de ejecutar pruebas; por ejemplo, un IDE podría permitir la ejecución de pruebas de Java en proceso. Sin embargo, el resultado de ejecutar la prueba como un proceso independiente debe considerarse autorizado. Si un proceso de prueba se ejecuta hasta completarse y termina normalmente con un código de salida de cero, la prueba fue aprobada. Cualquier otro resultado se considera una prueba fallida. En particular, escribir cualquiera de las strings PASS o FAIL en stdout no tiene importancia para el ejecutor de pruebas.

Si una prueba tarda demasiado en ejecutarse, excede algún límite de recursos o el ejecutor de pruebas detecta otro comportamiento prohibido, puede optar por finalizar la prueba y tratar la ejecución como un error. El ejecutor no debe informar que la prueba se completó luego de enviar una señal al proceso de prueba o a cualquier elemento secundario de este.

Se le otorga un tiempo limitado para que se ejecute la totalidad del destino de la prueba (no los métodos ni las pruebas individuales). El límite de tiempo de una prueba se basa en el atributo timeout según la siguiente tabla:

tiempo de espera Límite de tiempo (s)
short 60
Moderada 300
long 900
eterno 3,600

Las pruebas que no especifican explícitamente un tiempo de espera tienen una implícita basada en el size de la prueba de la siguiente manera:

size Etiqueta de tiempo de espera implícito
poco a poco short
medium Moderada
grandes long
enorme eterno

Una prueba "grande" sin un parámetro de configuración de tiempo de espera explícito tendrá 900 segundos para ejecutarse. Una prueba "media" con un tiempo de espera de "short" se asignará a 60 segundos.

A diferencia de timeout, size también determina el uso máximo supuesto de otros recursos (como RAM) cuando se ejecuta la prueba de forma local, como se describe en Definiciones comunes.

Todas las combinaciones de etiquetas size y timeout son legales, por lo que una declaración "enorme" puede declararse con un tiempo de espera de "corto". Es probable que haga cosas muy terribles muy rápido.

Las pruebas pueden mostrarse arbitrariamente rápido independientemente del tiempo de espera. Una prueba no se penaliza por un tiempo de espera excesivo, aunque se puede emitir una advertencia: generalmente, debes establecer tu tiempo de espera lo más acotado posible sin incurrir en fragilidades.

El tiempo de espera de la prueba se puede anular con la marca del bazel --test_timeout cuando se ejecuta de forma manual en condiciones que se sabe que son lentas. Los valores --test_timeout están en segundos. Por ejemplo, --test_timeout=120 establece el tiempo de espera de la prueba en dos minutos.

También hay un límite inferior recomendado para los tiempos de espera de prueba de la siguiente manera:

tiempo de espera Tiempo mínimo (s)
short 0
Moderada 30
long 300
eterno 900

Por ejemplo, si una prueba de "moderado" se completa en 5.5 s, considera configurar timeout = "short" o size = "small". Con la opción de línea de comandos --test_verbose_timeout_warnings de Bazel, se mostrarán las pruebas cuyo tamaño especificado sea demasiado grande.

Los tamaños de prueba y los tiempos de espera se especifican en el archivo BUILD de acuerdo con la especificación aquí. Si no se especifica, el tamaño predeterminado de una prueba será "medium".

Si el proceso principal de una prueba se cierra, pero algunos de sus elementos secundarios aún están en ejecución, el ejecutor de pruebas debe considerar que la ejecución se completó y contarla como un éxito o un fracaso según el código de salida observado del proceso principal. El ejecutor de pruebas puede finalizar cualquier proceso suelto. Las pruebas no deben filtrar procesos de esta manera.

Fragmentación de prueba

Las pruebas se pueden paralelizar mediante la fragmentación de pruebas. Consulta --test_sharding_strategy y shard_count para habilitar la fragmentación de pruebas. Cuando la fragmentación está habilitada, el ejecutor de pruebas se inicia una vez por fragmento. La variable de entorno TEST_TOTAL_SHARDS es la cantidad de fragmentos y TEST_SHARD_INDEX es el índice fragmentado, que comienza en 0. Los ejecutores usan esta información para seleccionar qué pruebas ejecutar, por ejemplo, mediante una estrategia de round-robin. No todos los ejecutores de pruebas admiten la fragmentación. Si un ejecutor admite la fragmentación, debe crear o actualizar la fecha de la última modificación del archivo especificado por TEST_SHARD_STATUS_FILE. De lo contrario, si --incompatible_check_sharding_support está habilitado, Bazel fallará la prueba si está fragmentada.

Condiciones iniciales

Cuando se ejecuta una prueba, el ejecutor de pruebas debe establecer ciertas condiciones iniciales.

El ejecutor de pruebas debe invocar cada prueba con la ruta del ejecutable de la prueba en argv[0]. Esta ruta de acceso debe ser relativa y estar debajo del directorio actual de la prueba (que se encuentra en el árbol de runfiles, consulta a continuación). El ejecutor de pruebas no debe pasar ningún otro argumento a una prueba, a menos que el usuario lo solicite explícitamente.

El bloque de entorno inicial se componerá de la siguiente manera:

Variable Valor Estado
HOME valor de $TEST_TMPDIR recomendado
LANG unset obligatorio
LANGUAGE unset obligatorio
LC_ALL unset obligatorio
LC_COLLATE unset obligatorio
LC_CTYPE unset obligatorio
LC_MESSAGES unset obligatorio
LC_MONETARY unset obligatorio
LC_NUMERIC unset obligatorio
LC_TIME unset obligatorio
LD_LIBRARY_PATH lista de directorios separados por dos puntos que contienen bibliotecas compartidas opcional
JAVA_RUNFILES valor de $TEST_SRCDIR obsoleto
LOGNAME valor de $USER obligatorio
PATH /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:. recomendado
PWD $TEST_SRCDIR/workspace-name recomendado
SHLVL 2 recomendado
TEST_INFRASTRUCTURE_FAILURE_FILE ruta absoluta a un archivo privado en un directorio que admita escritura (este archivo solo se debe usar para informar fallas que se originen en la infraestructura de pruebas, no como un mecanismo general para informar fallas inestables de pruebas) En este contexto, la infraestructura de pruebas se define como sistemas o bibliotecas que no son específicos de la prueba, pero pueden causar fallas de prueba debido al mal funcionamiento. La primera línea es el nombre del componente de la infraestructura de prueba que causó la falla, y la segunda es una descripción legible del error. (se ignoran las líneas adicionales). opcional
TEST_LOGSPLITTER_OUTPUT_FILE ruta absoluta a un archivo privado en un directorio que admite escritura (se usa para escribir el registro protobuffer de Logsplitter) opcional
TEST_PREMATURE_EXIT_FILE ruta absoluta a un archivo privado en un directorio que admite escritura (se usa para detectar llamadas a exit()) opcional
TEST_RANDOM_SEED Si se usa la opción --runs_per_test, TEST_RANDOM_SEED se establece en run number (comienza con 1) para cada ejecución de prueba individual. opcional
TEST_RUN_NUMBER Si se usa la opción --runs_per_test, TEST_RUN_NUMBER se establece en run number (comienza con 1) para cada ejecución de prueba individual. opcional
TEST_TARGET El nombre del destino que se está probando opcional
TEST_SIZE La prueba size opcional
TEST_TIMEOUT La prueba timeout en segundos opcional
TEST_SHARD_INDEX si se usa sharding opcional
TEST_SHARD_STATUS_FILE ruta al archivo para tocar a fin de indicar la compatibilidad con sharding opcional
TEST_SRCDIR ruta de acceso absoluta a la base del árbol de archivos de ejecución obligatorio
TEST_TOTAL_SHARDS total de shard count, si se usa sharding opcional
TEST_TMPDIR ruta de acceso absoluta a un directorio privado que admite escritura obligatorio
TEST_WORKSPACE el nombre del lugar de trabajo del repositorio local opcional
TEST_UNDECLARED_OUTPUTS_DIR ruta absoluta a un directorio privado que admite escritura (se usa para escribir resultados de pruebas no declarados) Se comprimirán los archivos escritos en el directorio TEST_UNDECLARED_OUTPUTS_DIR y se agregarán a un archivo outputs.zip en bazel-testlogs. opcional
TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR ruta absoluta a un directorio privado que admite escritura (se usa para escribir archivos .part y .pb de salida de prueba no declarados) opcional
TEST_WARNINGS_OUTPUT_FILE ruta de acceso absoluta a un archivo privado en un directorio que admite escritura (se usa para escribir advertencias de destino de prueba) opcional
TESTBRIDGE_TEST_ONLY valor de --test_filter, si se especifica opcional
TZ UTC obligatorio
USER valor de getpwuid(getuid())->pw_name obligatorio
XML_OUTPUT_FILE Ubicación en la que las acciones de prueba deben escribir un archivo de resultado XML de resultados de pruebas. De lo contrario, Bazel genera un archivo de salida XML predeterminado que une el registro de prueba como parte de la acción de prueba. El esquema XML se basa en el esquema de resultados de prueba JUnit. opcional
BAZEL_TEST Indica que bazel test ejecuta la prueba de ejecución obligatorio

El entorno puede contener entradas adicionales. Las pruebas no deben depender de la presencia, ausencia o valor de ninguna variable de entorno que no figure en la lista anterior.

El directorio de trabajo inicial será $TEST_SRCDIR/$TEST_WORKSPACE.

No se especifican el ID de proceso actual, el ID de grupo de procesos, el ID de sesión ni el ID de proceso superior. El proceso puede o no ser un líder de un grupo de procesos o un líder de sesión. El proceso puede o no tener una terminal de control. Es posible que el proceso tenga cero o más procesos en ejecución o secundarios. El proceso no debe tener varios subprocesos cuando el código de prueba obtiene el control.

El descriptor de archivo 0 (stdin) estará abierto para su lectura, pero no se especificará lo que se adjunta. Las pruebas no deben leer de ella. Los descriptores de archivos 1 (stdout) y 2 (stderr) estarán abiertos para escritura, pero no se especificarán los archivos a los que se adjuntan. Puede ser una terminal, una barra vertical, un archivo normal o cualquier otro elemento que se pueda escribir. Es posible que compartan una entrada en la tabla de archivos abiertos (es decir, que no pueden buscarlos de forma independiente). Las pruebas no deben heredar ningún otro descriptor de archivo abierto.

La umask inicial será 022 o 027.

No habrá ninguna alarma pendiente ni temporizador de intervalo.

La máscara inicial de los indicadores bloqueados debe estar vacía. Todos los indicadores se deben configurar con su acción predeterminada.

Los límites iniciales de recursos, tanto físicos como difíciles, se deben configurar de la siguiente manera:

Recurso Límite
RLIMIT_AS unlimited
RLIMIT_CORE sin especificar
RLIMIT_CPU unlimited
RLIMIT_DATA unlimited
RLIMIT_FSIZE unlimited
RLIMIT_LOCKS unlimited
RLIMIT_MEMLOCK unlimited
RLIMIT_MSGQUEUE sin especificar
RLIMIT_NICE sin especificar
RLIMIT_NOFILE al menos 1,024
RLIMIT_NPROC sin especificar
RLIMIT_RSS unlimited
RLIMIT_RTPRIO sin especificar
RLIMIT_SIGPENDING sin especificar
RLIMIT_STACK ilimitado o 2044 KB <= rlim <= 8192 KB

No se especifican los tiempos iniciales del proceso (como lo muestra times()) y el uso de recursos (como lo muestra getrusage()).

La política y la prioridad de la programación inicial no están especificadas.

Función del sistema host

Además de los aspectos del contexto del usuario bajo control directo del ejecutor de pruebas, el sistema operativo en el que se ejecutan las pruebas debe satisfacer ciertas propiedades para que sea válido.

Sistema de archivos

El directorio raíz observado en una prueba puede ser o no el directorio raíz real.

Se montará /proc.

Todas las herramientas de compilación deben estar presentes en las rutas de acceso absolutas en /usr que usa una instalación local.

Es posible que las rutas de acceso que comienzan con /home no estén disponibles. Las pruebas no deben acceder a ninguna de esas rutas.

/tmp podrá escribirse, pero las pruebas deben evitar el uso de estas rutas.

Las pruebas no deben suponer que hay una ruta constante disponible para su uso exclusivo.

Las pruebas no deben suponer que los horarios a veces están habilitados para cualquier sistema de archivos activado.

Usuarios y grupos

La raíz de los usuarios, nadie y la prueba de unidades deben existir. Los grupos raíz, nadie y eng deben existir.

Las pruebas deben ejecutarse como un usuario no raíz. Los ID de usuario reales y eficaces deben ser iguales; del mismo modo, los ID de grupo. Más allá de esto, el ID de usuario actual, el ID de grupo, el nombre de usuario y el nombre de grupo no se especificarán. El conjunto de ID de grupos complementarios no está especificado.

El ID de usuario y el ID de grupo actuales deben tener los nombres correspondientes que se pueden recuperar con getpwuid() y getgrgid(). Es posible que no sea el mismo para los ID de grupos complementarios.

El usuario actual debe tener un directorio principal. Es posible que no admita escritura. Las pruebas no deben intentar escribir en ella.

Herramientas de redes

El nombre de host no está especificado. Puede o no contener un punto. Resolver el nombre de host debe proporcionar una dirección IP del host actual. Resolver el nombre de host cortado después del primer punto también debe funcionar. El host host localhost debe resolverse.

Otros recursos

Las pruebas reciben al menos un núcleo de CPU. Es posible que otros estén disponibles, pero esto no está garantizado. No se especifican otros aspectos de rendimiento de este núcleo. Puedes aumentar la reserva a una mayor cantidad de núcleos de CPU si agregas la etiqueta “cpu:n” (donde n es un número positivo) a una regla de prueba. Si una máquina tiene menos núcleos de CPU totales que los solicitados, Bazel aún ejecutará la prueba. Si una prueba usa la fragmentación, cada fragmento se reservará la cantidad de núcleos de CPU que se especifican aquí.

Las pruebas pueden crear subprocesos, pero no procesar grupos o sesiones.

Existe un límite para la cantidad de archivos de entrada que puede consumir una prueba. Este límite está sujeto a cambios, pero se encuentra dentro del rango de decenas de miles de entradas.

Hora y fecha

La hora y la fecha actuales no están especificadas. La zona horaria del sistema no está especificada.

Es posible que Windows esté disponible o no. Las pruebas que necesitan un servidor X deben iniciar Xvfb.

Cómo probar la interacción con el sistema de archivos

Todas las rutas de acceso a archivos especificadas en las variables de entorno de prueba apuntan a algún lugar del sistema de archivos local, a menos que se especifique lo contrario.

Las pruebas deben crear archivos solo dentro de los directorios especificados por $TEST_TMPDIR y $TEST_UNDECLARED_OUTPUTS_DIR (si están configurados).

Inicialmente, estos directorios estarán vacíos.

Las pruebas no deben intentar quitar, modificar o modificar estos directorios.

Estos directorios pueden ser vínculos simbólicos.

El tipo de sistema de archivos de $TEST_TMPDIR/. no se especifica.

Las pruebas también pueden escribir archivos .part en $TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR para anotar archivos de salida no declarados.

En casos excepcionales, una prueba puede verse forzada a crear archivos en /tmp. Por ejemplo, los límites de longitud de la ruta de acceso para los sockets de dominio Unix, por lo general, requieren la creación del socket en /tmp. Bazel no podrá hacer un seguimiento de esos archivos. La prueba en sí debe tener cuidado de ser hermética, usar rutas de acceso únicas para evitar entrar en conflicto con otras y, al mismo tiempo, ejecutar pruebas y procesos que no son de prueba, y limpiar los archivos que crea en /tmp.

Algunos frameworks de prueba populares, como JUnit4 TemporaryFolder o Go TempDir, tienen sus propias formas de crear un directorio temporal en /tmp. Estos frameworks de prueba incluyen una funcionalidad que limpia archivos en /tmp, por lo que puedes usarlos aunque creen archivos fuera de TEST_TMPDIR.

Las pruebas deben acceder a las entradas a través del mecanismo de runfiles o a otras partes del entorno de ejecución que están diseñadas específicamente para hacer que los archivos de entrada estén disponibles.

Las pruebas no deben acceder a otras salidas del sistema de compilación en rutas de acceso inferidas de la ubicación de su propio ejecutable.

No se especifica si el árbol de archivos de ejecución contiene archivos normales, vínculos simbólicos o una mezcla. El árbol de runfiles puede contener symlinks a directorios. Las pruebas deben evitar el uso de rutas de acceso que contengan componentes .. dentro del árbol de archivos de ejecución.

Ningún directorio, archivo o symlink dentro del árbol de runfiles (incluidas las rutas que atraviesan symlinks) se debe poder escribir. (El resultado es que el directorio de trabajo inicial no debe escribirse). Las pruebas no deben suponer que ninguna parte de los runfiles puede escribirse o que es propiedad del usuario actual (por ejemplo, es posible que fallen chmod y chgrp).

El árbol de runfiles (incluidas las rutas que atraviesan symlinks) no debe cambiar durante la ejecución de la prueba. Los directorios superiores y las activaciones de sistemas de archivos no deben cambiar de ninguna manera que afecte el resultado de resolver una ruta de acceso dentro del árbol de archivos de ejecución.

Para detectar la salida anticipada, una prueba puede crear un archivo en la ruta que especifica TEST_PREMATURE_EXIT_FILE al inicio y quitarlo al salir. Si Bazel ve el archivo cuando finaliza la prueba, supondrá que la prueba finalizó de forma prematura y se la marcará como fallida.

Convenciones de etiquetas

Algunas etiquetas de las reglas de prueba tienen un significado especial. Consulta también la Enciclopedia de compilación de Bazel en el atributo tags.

Etiqueta Significado
exclusive no ejecutar ninguna otra prueba al mismo tiempo
external La prueba tiene una dependencia externa. Inhabilita el almacenamiento en caché de la prueba.
large Convención test_suite; paquete de pruebas grandes
manual * No incluyas el destino de prueba en patrones de comodín de comodines, como :..., :* o :all.
medium Convención test_suite; paquete de pruebas de nivel intermedio
small Convención test_suite; paquete de pruebas pequeñas
smoke test_suite. Significa que se debe ejecutar antes de confirmar los cambios de código en el sistema de control de versión.

Archivos en ejecución

En el siguiente ejemplo, supongamos que hay una regla *_binary() etiquetada como //foo/bar:unittest, con una dependencia de tiempo de ejecución en la regla etiquetada //deps/server:server.

Ubicación

El directorio runfiles para un //foo/bar:unittest de destino es el directorio $(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles. Esta ruta se conoce como runfiles_dir.

Dependencias

El directorio runfiles se declara como una dependencia en tiempo de compilación de la regla *_binary(). El directorio de archivos de ejecución en sí depende del conjunto de archivos de BUILD que afectan a la regla *_binary() o a cualquiera de sus dependencias en tiempo de compilación o ejecución. La modificación de los archivos fuente no afecta la estructura del directorio runfiles y, por lo tanto, no activa ninguna recompilación.

Contenido

El directorio runfiles contiene lo siguiente:

  • Vínculos simbólicos a dependencias en tiempo de ejecución: cada OutputFile y CommandRule que es una dependencia en tiempo de ejecución de la regla *_binary() se representa mediante un symlink en el directorio runfiles. El nombre del symlink es $(WORKSPACE)/package_name/rule_name. Por ejemplo, el symlink para el servidor tendría el nombre $(WORKSPACE)/deps/server/server y la ruta completa sería $(WORKSPACE)/foo/bar/unittest.runfiles/$(WORKSPACE)/deps/server/server. El destino del symlink es el OutputFileName() del OutputFile o CommandRule, expresado como una ruta de acceso absoluta. Por lo tanto, el destino del symlink podría ser $(WORKSPACE)/linux-dbg/deps/server/42/server.
  • Vínculos simbólicos a subrunfiles: para cada *_binary() Z que es una dependencia en tiempo de ejecución de *_binary() C, hay un segundo vínculo en el directorio de archivos de ejecución C para los archivos de ejecución de Z. El nombre del symlink es $(WORKSPACE)/package_name/rule_name.runfiles. El destino del symlink es el directorio runfiles. Por ejemplo, todos los subprogramas comparten un directorio común de archivos de ejecución.