Uma especificação exaustiva do ambiente de execução de teste.
Contexto
A linguagem BUILD do Bazel inclui regras que podem ser usadas para definir programas de teste automatizados em vários idiomas.
Os testes são executados usando bazel test
.
Os usuários também podem executar binários de teste diretamente. Isso é permitido, mas não recomendado, porque essa invocação não obedece aos mandatos descritos abaixo.
Os testes precisam ser herméticos, ou seja, acessar apenas os recursos em que têm uma dependência declarada. Se os testes não forem herméticos, eles não vão gerar resultados historicamente reproduzíveis. Isso pode ser um problema significativo para a descoberta de culpados (determinar qual mudança quebrou um teste), a capacidade de auditoria de engenharia de lançamento e o isolamento de recursos de testes (as estruturas de teste automatizadas não devem DDOS um servidor porque alguns testes acontecem para falar com ele).
Objetivo
O objetivo desta página é estabelecer formalmente o ambiente de execução e o comportamento esperado dos testes do Bazel. Ele também vai impor requisitos ao executor de testes e ao sistema de build.
A especificação do ambiente de teste ajuda os criadores de testes a evitar depender de comportamentos não especificados e, assim, dá à infraestrutura de teste mais liberdade para fazer mudanças na implementação. A especificação corrige algumas falhas que atualmente permitem que muitos testes sejam aprovados, mesmo não sendo adequadamente herméticos, determinísticos e reentrantes.
Esta página é normativa e oficial. Se essa especificação e o comportamento implementado do executor de teste discordarem, a especificação terá precedência.
Especificação proposta
As palavras-chave "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" e "OPTIONAL" devem ser interpretadas conforme descrito na RFC 2119 do IETF.
Finalidade dos testes
O objetivo dos testes do Bazel é confirmar alguma propriedade dos arquivos de origem verificados no repositório. Nesta página, "arquivos de origem" inclui dados de teste, saídas de referência e qualquer outra coisa mantida sob controle de versão. Um usuário escreve um teste para declarar uma invariante que ele espera ser mantida. Outros usuários executam o teste mais tarde para verificar se a invariante foi violada. Se o teste depender de variáveis que não sejam arquivos de origem (não herméticas), o valor dele será reduzido, porque os usuários posteriores não poderão ter certeza de que as mudanças deles são as culpadas quando o teste parar de ser aprovado.
Portanto, o resultado de um teste deve depender apenas de:
- arquivos de origem em que o teste tem uma dependência declarada
- produtos do sistema de build em que o teste tem uma dependência declarada
- recursos cujo comportamento é garantido pelo executor de testes para permanecer constante
No momento, esse comportamento não é aplicado. No entanto, os executores de teste reservam o direito de adicionar essa aplicação no futuro.
Função do sistema de build
As regras de teste são análogas às regras binárias, já que cada uma precisa gerar um programa executável. Para algumas linguagens, esse é um programa stub que combina um arnês específico da linguagem com o código de teste. As regras de teste também precisam gerar outras saídas. Além do executável de teste principal, o executor de testes precisa de um manifesto de runfiles, arquivos de entrada que devem ser disponibilizados para o teste durante a execução. Ele também pode precisar de informações sobre o tipo, o tamanho e as tags de um teste.
O sistema de build pode usar os runfiles para entregar código e dados. Isso pode ser usado como uma otimização para reduzir o tamanho de cada binário de teste compartilhando arquivos entre testes, como usando vinculação dinâmica. O sistema de build precisa garantir que o executável gerado carregue esses arquivos usando a imagem runfiles fornecida pelo executor de testes, em vez de referências codificadas a locais absolutos na árvore de origem ou de saída.
Função do executor de teste
Do ponto de vista do executor de testes, cada teste é um programa que pode ser
invocado com execve()
. Pode haver outras maneiras de executar testes. Por exemplo, uma IDE pode permitir a execução de testes Java no processo. No entanto, o resultado
de executar o teste como um processo independente deve ser considerado confiável. Se
um processo de teste for concluído e terminar normalmente com um código de saída
zero, o teste será aprovado. Qualquer outro resultado é considerado uma falha no teste. Em
particular, gravar qualquer uma das strings PASS
ou FAIL
em stdout não tem
significado para o executor de testes.
Se um teste demorar muito para ser executado, exceder algum limite de recurso ou se o executor de testes detectar um comportamento proibido, ele poderá encerrar o teste e tratar a execução como uma falha. O executor não pode informar que o teste foi aprovado depois de enviar um sinal para o processo de teste ou qualquer um dos filhos dele.
O destino de teste inteiro (não métodos ou testes individuais) recebe um período limitado para ser executado até a conclusão. O limite de tempo de um teste é baseado no atributo
timeout
, de acordo
com a tabela a seguir:
timeout | Limite de tempo (seg.) |
---|---|
short | 60 |
moderada | 300 |
long | 900 |
eterno | 3600 |
Os testes que não especificam explicitamente um tempo limite têm um implícito com base no
size
do teste da seguinte maneira:
tamanho | Rótulo de tempo limite implícito |
---|---|
pequeno | short |
média | moderada |
grande | long |
enorme | eterno |
Um teste "grande" sem uma configuração de tempo limite explícita terá 900 segundos para ser executado. Um teste "médio" com um tempo limite "curto" terá 60 segundos.
Ao contrário de timeout
, o size
também determina o uso máximo presumido de outros recursos (como RAM) ao executar o teste localmente, conforme descrito em Definições comuns.
Todas as combinações de rótulos size
e timeout
são válidas. Portanto, um teste "enorme" pode ser declarado como tendo um tempo limite "curto". Presumivelmente, ele faria algumas coisas horríveis muito rapidamente.
Os testes podem retornar resultados arbitrariamente rápidos, independente do tempo limite. Um teste não é penalizado por um tempo limite muito generoso, mas um aviso pode ser emitido: geralmente, defina o tempo limite o mais curto possível sem incorrer em instabilidade.
O tempo limite do teste pode ser substituído com a flag --test_timeout
do Bazel ao
executar manualmente em condições que são conhecidas por serem lentas. Os valores de --test_timeout
estão em segundos. Por exemplo, --test_timeout=120
define o tempo limite do teste como dois minutos.
Há também um limite inferior recomendado para tempos limite de teste, da seguinte forma:
timeout | Tempo mínimo (seg.) |
---|---|
short | 0 |
moderada | 30 |
long | 300 |
eterno | 900 |
Por exemplo, se um teste "moderado" for concluído em 5,5 segundos, considere definir timeout =
"short"
ou size = "small"
. Usar a opção de linha de comando --test_verbose_timeout_warnings
do bazel mostra os testes cujo tamanho especificado é muito grande.
Os tamanhos e tempos limite dos testes são especificados no arquivo BUILD de acordo com a especificação aqui. Se não for especificado, o tamanho padrão de um teste será "médio".
Se o processo principal de um teste for encerrado, mas alguns dos filhos dele ainda estiverem em execução, o executor de testes vai considerar a execução concluída e a contará como um sucesso ou falha com base no código de saída observado no processo principal. O executor de testes pode encerrar processos soltos. Os testes não podem vazar processos dessa forma.
Fragmentação de testes
Os testes podem ser paralelizados por fragmentação. Consulte
--test_sharding_strategy
e shard_count
para
ativar o sharding de teste. Quando a fragmentação está ativada, o executor de testes é iniciado uma vez por fragmento. A variável de ambiente TEST_TOTAL_SHARDS
é o número de fragmentos, e TEST_SHARD_INDEX
é o
índice de fragmento, começando em 0. Os executores usam essas informações para selecionar quais testes
executar, por exemplo, usando uma estratégia de rodízio. Nem todos os executores de teste são compatíveis com
sharding. Se um executor oferecer suporte a fragmentação, ele precisará criar ou atualizar a data da última modificação do arquivo especificado por TEST_SHARD_STATUS_FILE
. Caso contrário, se
--incompatible_check_sharding_support
estiver ativado, o Bazel vai falhar no teste se ele for fragmentado.
Condições iniciais
Ao executar um teste, o executor precisa estabelecer determinadas condições iniciais.
O executor de testes precisa invocar cada teste com o caminho para o executável do teste em
argv[0]
. O caminho precisa ser relativo e estar abaixo do diretório atual do teste (que está na árvore de runfiles, veja abaixo). O executor de testes não pode transmitir outros argumentos a um teste, a menos que o usuário solicite explicitamente.
O bloco de ambiente inicial será composto da seguinte maneira:
Variável | Valor | Status |
---|---|---|
HOME |
valor de $TEST_TMPDIR |
recomendado |
LANG |
unset | obrigatório |
LANGUAGE |
unset | obrigatório |
LC_ALL |
unset | obrigatório |
LC_COLLATE |
unset | obrigatório |
LC_CTYPE |
unset | obrigatório |
LC_MESSAGES |
unset | obrigatório |
LC_MONETARY |
unset | obrigatório |
LC_NUMERIC |
unset | obrigatório |
LC_TIME |
unset | obrigatório |
LD_LIBRARY_PATH |
lista separada por dois-pontos de diretórios que contêm bibliotecas compartilhadas | opcional |
JAVA_RUNFILES |
valor de $TEST_SRCDIR |
descontinuado |
LOGNAME |
valor de $USER |
obrigatório |
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 |
caminho absoluto para um arquivo particular em um diretório gravável. Esse arquivo só deve ser usado para informar falhas originadas da infraestrutura de teste, não como um mecanismo geral para informar falhas instáveis de testes. Nesse contexto, a infraestrutura de teste é definida como sistemas ou bibliotecas que não são específicas para testes, mas podem causar falhas de teste por mau funcionamento. A primeira linha é o nome do componente da infraestrutura de teste que causou a falha, e a segunda é uma descrição legível da falha. As linhas extras são ignoradas.) | opcional |
TEST_LOGSPLITTER_OUTPUT_FILE |
caminho absoluto para um arquivo particular em um diretório gravável (usado para gravar o registro do protobuffer do Logsplitter) | opcional |
TEST_PREMATURE_EXIT_FILE |
caminho absoluto para um arquivo particular em um diretório gravável (usado para
capturar chamadas para exit() ) |
opcional |
TEST_RANDOM_SEED |
Se a opção --runs_per_test for usada, TEST_RANDOM_SEED será definido como o run number (começando com 1) para cada execução de teste individual. |
opcional |
TEST_RUN_NUMBER |
Se a opção --runs_per_test for usada, TEST_RUN_NUMBER será definido como o run number (começando com 1) para cada execução de teste individual. |
opcional |
TEST_TARGET |
O nome do destino que está sendo testado | opcional |
TEST_SIZE |
O teste size |
opcional |
TEST_TIMEOUT |
O teste timeout em segundos |
opcional |
TEST_SHARD_INDEX |
índice de fragmento, se sharding for usado |
opcional |
TEST_SHARD_STATUS_FILE |
caminho para o arquivo a ser tocado para indicar suporte a sharding |
opcional |
TEST_SRCDIR |
caminho absoluto para a base da árvore de runfiles | obrigatório |
TEST_TOTAL_SHARDS |
total
shard count ,
se sharding for usado |
opcional |
TEST_TMPDIR |
caminho absoluto para um diretório particular gravável | obrigatório |
TEST_WORKSPACE |
o nome do espaço de trabalho do repositório local | opcional |
TEST_UNDECLARED_OUTPUTS_DIR |
caminho absoluto para um diretório particular gravável (usado para gravar saídas de teste não declaradas). Todos os arquivos gravados no diretório
TEST_UNDECLARED_OUTPUTS_DIR serão compactados e
adicionados a um arquivo outputs.zip em
bazel-testlogs . |
opcional |
TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR |
Caminho absoluto para um diretório particular gravável (usado para gravar arquivos de anotação de saída de teste não declarados .part e .pb ). |
opcional |
TEST_WARNINGS_OUTPUT_FILE |
caminho absoluto para um arquivo particular em um diretório gravável (usado para gravar avisos de destino de teste) | opcional |
TESTBRIDGE_TEST_ONLY |
valor de
--test_filter ,
se especificado |
opcional |
TZ |
UTC |
obrigatório |
USER |
valor de getpwuid(getuid())->pw_name |
obrigatório |
XML_OUTPUT_FILE |
Local em que as ações de teste devem gravar um arquivo de saída XML de resultado do teste. Caso contrário, o Bazel vai gerar um arquivo XML padrão que envolve o registro de teste como parte da ação de teste. O esquema XML é baseado no esquema de resultados de testes do JUnit. | opcional |
BAZEL_TEST |
Significa que o executável de teste está sendo executado por bazel test |
obrigatório |
O ambiente pode conter outras entradas. Os testes não podem depender da presença, ausência ou valor de qualquer variável de ambiente não listada acima.
O diretório de trabalho inicial será $TEST_SRCDIR/$TEST_WORKSPACE
.
O ID do processo atual, o ID do grupo de processos, o ID da sessão e o ID do processo pai não são especificados. O processo pode ou não ser um líder de grupo de processos ou de sessão. O processo pode ou não ter um terminal de controle. O processo pode ter zero ou mais processos filhos em execução ou não coletados. O processo não pode ter várias linhas de execução quando o código de teste ganha controle.
O descritor de arquivo 0 (stdin
) precisa estar aberto para leitura, mas não é especificado a que ele está anexado. Os testes não podem ler dele. Os descritores de arquivo 1 (stdout
) e 2 (stderr
) precisam estar abertos para gravação, mas não é especificado a que eles estão anexados. Pode ser um terminal, um pipe, um arquivo normal ou qualquer outra coisa em que caracteres possam ser gravados. Eles podem compartilhar uma entrada na tabela de arquivos abertos, o que significa que não podem buscar de forma independente. Os testes não podem herdar outros descritores de arquivos abertos.
A umask inicial precisa ser 022
ou 027
.
Nenhum alarme ou timer de intervalo pode estar pendente.
A máscara inicial de indicadores bloqueados precisa estar vazia. Todos os indicadores precisam ser definidos como a ação padrão.
Os limites iniciais de recursos, flexíveis e rígidos, precisam ser definidos da seguinte maneira:
Recurso | Limite |
---|---|
RLIMIT_AS |
ilimitado |
RLIMIT_CORE |
não especificado |
RLIMIT_CPU |
ilimitado |
RLIMIT_DATA |
ilimitado |
RLIMIT_FSIZE |
ilimitado |
RLIMIT_LOCKS |
ilimitado |
RLIMIT_MEMLOCK |
ilimitado |
RLIMIT_MSGQUEUE |
não especificado |
RLIMIT_NICE |
não especificado |
RLIMIT_NOFILE |
pelo menos 1024 |
RLIMIT_NPROC |
não especificado |
RLIMIT_RSS |
ilimitado |
RLIMIT_RTPRIO |
não especificado |
RLIMIT_SIGPENDING |
não especificado |
RLIMIT_STACK |
ilimitado ou 2044 KB <= rlim <= 8192 KB |
Os tempos de processo iniciais (retornados por times()
) e a utilização de recursos (retornados por getrusage()
) não são especificados.
A política e a prioridade de programação iniciais não são especificadas.
Função do sistema host
Além dos aspectos do contexto do usuário sob controle direto do executor de testes, o sistema operacional em que os testes são executados precisa atender a determinadas propriedades para que uma execução de teste seja válida.
Sistema de arquivos
O diretório raiz observado por um teste pode ou não ser o diretório raiz real.
O /proc
será ativado.
Todas as ferramentas de build precisam estar nos caminhos absolutos em /usr
usados por uma instalação local.
Os caminhos que começam com /home
podem não estar disponíveis. Os testes não podem acessar esses caminhos.
/tmp
precisa ser gravável, mas os testes devem evitar usar esses caminhos.
Os testes não podem presumir que um caminho constante está disponível para uso exclusivo.
Os testes não podem presumir que os atimes estão ativados para qualquer sistema de arquivos montado.
Usuários e grupos
Os usuários root, nobody e unittest precisam existir. As raízes "groups", "nobody" e "eng" precisam existir.
Os testes precisam ser executados como um usuário não raiz. Os IDs de usuário reais e efetivos precisam ser iguais, assim como os IDs de grupo. Além disso, o ID do usuário atual, o ID do grupo, o nome do usuário e o nome do grupo não são especificados. O conjunto de IDs de grupo suplementares não foi especificado.
O ID do usuário e do grupo atuais precisam ter nomes correspondentes que podem ser
recuperados com getpwuid()
e getgrgid()
. O mesmo pode não ser verdade para IDs de grupos suplementares.
O usuário atual precisa ter um diretório principal. Ele pode não ser gravável. Os testes não podem tentar gravar nele.
Rede
O nome de host não foi especificado. Ele pode ou não conter um ponto. A resolução do nome do host precisa fornecer um endereço IP do host atual. Resolver o corte do nome do host após o primeiro ponto também precisa funcionar. O nome do host localhost precisa ser resolvido.
Outros recursos
Os testes recebem pelo menos um núcleo de CPU. Outros podem estar disponíveis, mas isso não é garantido. Outros aspectos de desempenho desse núcleo não são especificados. É possível aumentar a reserva para um número maior de núcleos de CPU adicionando a tag "cpu:n" (em que n é um número positivo) a uma regra de teste. Se uma máquina tiver menos núcleos de CPU totais do que o solicitado, o Bazel ainda vai executar o teste. Se um teste usar fragmentação, cada fragmento individual vai reservar o número de núcleos de CPU especificado aqui.
Os testes podem criar subprocessos, mas não grupos de processos ou sessões.
Há um limite para o número de arquivos de entrada que um teste pode consumir. Esse limite está sujeito a mudanças, mas atualmente está na faixa de dezenas de milhares de entradas.
Hora e data
A hora e a data atuais não foram especificadas. O fuso horário do sistema não foi especificado.
O X Windows pode ou não estar disponível. Os testes que precisam de um servidor X devem iniciar o Xvfb.
Testar a interação com o sistema de arquivos
Todos os caminhos de arquivo especificados em variáveis de ambiente de teste apontam para algum lugar no sistema de arquivos local, a menos que seja especificado de outra forma.
Os testes só podem criar arquivos nos diretórios especificados por
$TEST_TMPDIR
e $TEST_UNDECLARED_OUTPUTS_DIR
(se definidos).
Esses diretórios vão estar vazios inicialmente.
Os testes não podem tentar remover, chmod ou alterar esses diretórios.
Esses diretórios podem ser links simbólicos.
O tipo de sistema de arquivos de $TEST_TMPDIR/.
permanece não especificado.
Os testes também podem gravar arquivos .part no
$TEST_UNDECLARED_OUTPUTS_ANNOTATIONS_DIR
para anotar arquivos de saída não declarados.
Em casos raros, um teste pode ser forçado a criar arquivos em /tmp
. Por exemplo, os limites de comprimento de caminho para soquetes de domínio Unix geralmente exigem a criação do soquete em /tmp
. O Bazel não poderá rastrear esses arquivos. O teste precisa ser hermético, usar caminhos exclusivos para evitar conflitos com outros testes e processos que não são de teste em execução simultânea e limpar os arquivos criados em /tmp
.
Alguns frameworks de teste conhecidos, como
JUnit4 TemporaryFolder
ou Go TempDir
, têm
maneiras próprias de criar um diretório temporário em /tmp
. Esses frameworks de teste incluem funcionalidades que limpam arquivos em /tmp
. Portanto, você pode usá-los mesmo que eles criem arquivos fora de TEST_TMPDIR
.
Os testes precisam acessar as entradas pelo mecanismo runfiles ou outras partes do ambiente de execução especificamente destinadas a disponibilizar arquivos de entrada.
Os testes não podem acessar outras saídas do sistema de build em caminhos inferidos da localização do próprio executável.
Não é especificado se a árvore de runfiles contém arquivos regulares, links
simbólicos ou uma mistura. A árvore de runfiles pode conter links simbólicos para diretórios.
Os testes precisam evitar o uso de caminhos que contenham componentes ..
na árvore
runfiles.
Nenhum diretório, arquivo ou link simbólico na árvore de arquivos de execução (incluindo caminhos que atravessam links simbólicos) pode ser gravável. Portanto, o diretório de trabalho inicial não pode ser gravável. Os testes não podem presumir que qualquer parte dos
runfiles pode ser gravada ou pertence ao usuário atual. Por exemplo, chmod
e chgrp
podem
falhar.
A árvore de runfiles (incluindo caminhos que atravessam symlinks) não pode mudar durante a execução do teste. Os diretórios principais e as montagens do sistema de arquivos não podem mudar de forma alguma que afete o resultado da resolução de um caminho na árvore de runfiles.
Para detectar uma saída antecipada, um teste pode criar um arquivo no caminho especificado por
TEST_PREMATURE_EXIT_FILE
ao iniciar e removê-lo ao sair. Se o Bazel encontrar o
arquivo quando o teste terminar, ele vai presumir que o teste foi encerrado prematuramente e
marcará como falha.
Convenções de inclusão de tag
Algumas tags nas regras de teste têm um significado especial. Consulte também a
Bazel Build Encyclopedia sobre o atributo tags
(link em inglês).
Tag | Significado |
---|---|
exclusive |
não execute outro teste ao mesmo tempo |
external |
o teste tem uma dependência externa; desative o cache de teste |
large |
Convenção test_suite ; conjunto de testes grandes |
manual * |
não inclua o destino de teste em padrões de destino curinga, como
:... , :* ou :all |
medium |
Convenção test_suite ; pacote de testes médios |
small |
Convenção test_suite ; conjunto de testes pequenos. |
smoke |
Convenção test_suite , ou seja, precisa ser executado antes de
confirmar mudanças no código no sistema de controle de versões. |
Runfiles
A seguir, suponha que haja uma regra *_binary() chamada
//foo/bar:unittest
, com uma dependência de tempo de execução na regra chamada
//deps/server:server
.
Local
O diretório de arquivos de execução para um destino //foo/bar:unittest
é o diretório
$(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles
. Esse caminho é chamado de runfiles_dir
.
Dependências
O diretório de runfiles é declarado como uma dependência de tempo de compilação da
regra *_binary()
. O diretório runfiles depende do conjunto de arquivos BUILD
que afetam a regra *_binary()
ou qualquer uma das dependências
de tempo de compilação ou de execução. A modificação dos arquivos de origem não afeta a estrutura do diretório
runfiles e, portanto, não aciona nenhuma recompilação.
Índice
O diretório runfiles contém o seguinte:
- Links simbólicos para dependências de tempo de execução: cada OutputFile e CommandRule que
é uma dependência de tempo de execução da regra
*_binary()
é representado por um link simbólico no diretório runfiles. O nome do link simbólico é$(WORKSPACE)/package_name/rule_name
. Por exemplo, o link simbólico para o servidor seria chamado de$(WORKSPACE)/deps/server/server
, e o caminho completo seria$(WORKSPACE)/foo/bar/unittest.runfiles/$(WORKSPACE)/deps/server/server
. O destino do link simbólico é o OutputFileName() do OutputFile ou CommandRule, expresso como um caminho absoluto. Assim, o destino do link simbólico pode ser$(WORKSPACE)/linux-dbg/deps/server/42/server
. - Links simbólicos para subarquivos de execução: para cada
*_binary()
Z que é uma dependência de tempo de execução de*_binary()
C, há um segundo link no diretório de arquivos de execução de C para os arquivos de execução de Z. O nome do link simbólico é$(WORKSPACE)/package_name/rule_name.runfiles
. O destino do link simbólico é o diretório de arquivos de execução. Por exemplo, todos os subprogramas compartilham um diretório comum de arquivos de execução.