Uma especificação completa do ambiente de execução do teste.
Contexto
A linguagem BUILD do Bazel inclui regras que podem ser usadas para definir programas de teste automatizados em várias linguagens.
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 endossado, porque essa invocação não aderirá às autorizações descritas 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 adequadamente herméticos, não fornecerão resultados historicamente reproduzíveis. Isso pode ser um problema significativo para a descoberta do culpado (determinar qual alteração quebrou um teste), auditabilidade de engenharia de versão e isolamento de recursos de testes (frameworks de teste automatizados não podem DDOS um servidor, porque alguns testes se comunicam 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 impõe requisitos ao executor de testes e ao sistema de build.
A especificação do ambiente de teste ajuda os autores de teste a evitar depender de comportamentos não especificados e, assim, oferece à infraestrutura de teste mais liberdade para fazer alterações de implementação. A especificação corrige algumas falhas que atualmente permitem a aprovação de muitos testes, mesmo não sendo adequadamente hermética, determinística e reentrante.
Esta página precisa ser regulatória e autorizada. Se essa especificação e o comportamento implementado do executor de testes discordarem, a especificação terá precedência.
Especificação proposta
As palavras-chave "DEVE", "NÃO DEVE", "PRECISA", "DEVE", "NÃO DEVERÁ", "DEVE", "NÃO DEVE", "RECOMENDADO", "PODE" e "OPCIONAL" devem ser interpretadas conforme descrito no IETF RFC 2119.
Objetivo dos testes
O objetivo dos testes do Bazel é confirmar alguma propriedade dos arquivos de origem registrados no repositório. Nesta página, "arquivos de origem" incluem dados de teste, saídas douradas e qualquer outra coisa mantida sob controle de versão. Um usuário grava um teste para declarar uma invariante que espera que seja mantida. Outros usuários executam o teste mais tarde para verificar se a invariante foi corrompida. Se o teste depender de qualquer variável que não seja os arquivos de origem (não hermético), o valor dele será reduzido, porque os usuários posteriores não poderão ter certeza de que as alterações são falhas quando o teste for interrompido.
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 com comportamento que é garantido pelo executor de testes para permanecer constante
Atualmente, esse comportamento não é aplicado. No entanto, os executores de teste reservam o direito de adicionar essa aplicação no futuro.
Papel do sistema de build
As regras de teste são análogas às binárias, em que cada uma precisa produzir um programa executável. Para algumas linguagens, este é um programa stub que combina um arcabouço específico de linguagens com o código de teste. As regras de teste também precisam produzir outras saídas. Além do executável principal, o executor precisa de um manifesto de runfiles, arquivos de entrada que precisam ser disponibilizados para o teste no ambiente de execução e pode precisar de informações sobre o tipo, o tamanho e as tags de um teste.
O sistema de compilação pode usar os arquivos de execução para entregar códigos e dados. Isso pode ser usado como uma otimização para diminuir cada binário de teste compartilhando arquivos entre testes, por exemplo, com o uso de links dinâmicos. O sistema de build precisa garantir que o executável gerado carregue esses arquivos pela imagem fornecida pelo executor de testes, em vez de referências fixadas no código a locais absolutos na árvore de origem ou saída.
Papel 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,
um ambiente de desenvolvimento integrado pode permitir a execução de testes Java durante o processo. No entanto, o resultado
da execução do teste como um processo independente precisa ser considerado autoritativo. Se
um processo de teste é executado até ser concluído e normalmente termina com um código de saída
zero, o teste foi aprovado. Qualquer outro resultado é considerado uma falha no teste. Em
particular, a gravação de 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 recursos ou o executor de testes detectar um comportamento proibido, ele poderá optar por 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 filho dele.
Todo o destino do teste (não métodos ou testes individuais) tem um tempo 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:
pedido de tempo | Limite de tempo (s) |
---|---|
short | 60 |
moderado | 300 |
long | 900 |
eterno | 3.600 |
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 |
medium | moderado |
grande | long |
enorme | eterno |
Um teste "grande" sem configuração explícita de tempo limite será alocado para 900 segundos para execução. Um teste "médio" com um tempo limite de "curto" é alocado de 60 segundos.
Ao contrário de timeout
, o size
também determina o pico de uso 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 legais. Portanto, um teste "enorme"
pode ser declarado como tendo um tempo limite "curto". Presumimos que isso faria algumas coisas
muito horríveis de forma muito rápida.
Os testes podem retornar arbitrariamente rápido, independentemente do tempo limite. Um teste não é penalizado por um tempo limite excessivo, embora um aviso possa ser emitido: geralmente, defina o tempo limite o mais restrito possível, sem gerar inconsistências.
O tempo limite do teste pode ser substituído pela sinalização bazel --test_timeout
quando
é executado manualmente em condições conhecidas como lentas. Os valores --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, conforme abaixo:
pedido de tempo | Tempo mínimo (s) |
---|---|
short | 0 |
moderado | 30 |
long | 300 |
eterno | 900 |
Por exemplo, se um teste "moderado" for concluído em 5,5 segundos, defina timeout =
"short"
ou size = "small"
. A opção de linha de comando --test_verbose_timeout_warnings
do Bazel mostra os testes com o tamanho especificado muito grande.
Tamanhos e tempos limite de teste são especificados no arquivo BUILD de acordo com a especificação aqui. Se não for especificado, o tamanho de um teste será padronizada como "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 precisará considerar a execução como concluída e contabilizá-la como sucesso ou falha com base no código de saída observado do processo principal. O executor de teste pode encerrar processos dispersos. Os testes não podem vazar processos dessa maneira.
Fragmentação de testes
Os testes podem ser carregados em paralelo por meio da fragmentação de testes. Consulte
--test_sharding_strategy
e shard_count
para
ativar a fragmentação de testes. 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 fragmentos, começando em 0. Os executores usam essas informações para selecionar quais testes
serão executados. Por exemplo, usando uma estratégia de round-robin. Nem todos os executores de teste são compatíveis
com a fragmentação. Se um executor aceita fragmentação, ele precisa criar ou atualizar a data da última
modificação do arquivo especificado por
TEST_SHARD_STATUS_FILE
. Caso contrário, se o
--incompatible_check_sharding_support
estiver ativado, o Bazel vai falhar no teste se estiver fragmentado.
Condições iniciais
Ao executar um teste, o executor precisa estabelecer determinadas condições iniciais.
O executor precisa invocar cada teste com o caminho para o executável do teste em
argv[0]
. Esse caminho precisa ser relativo e estar abaixo do diretório atual do teste,
que está na árvore de arquivos de execução, conforme abaixo. O executor de testes não pode transmitir
outros argumentos a um teste, a menos que o usuário o solicite explicitamente.
O bloco de ambiente inicial precisa 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 de diretórios separados por dois pontos contendo 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ó pode ser usado para relatar falhas originadas da infraestrutura de testes, não como um mecanismo geral para relatar falhas instáveis de testes. Nesse contexto, a infraestrutura de testes é definida como sistemas ou bibliotecas que não são específicos ao teste, mas que podem causar falhas devido ao mau funcionamento. A primeira linha é o nome do componente de infraestrutura de teste que causou a falha. A segunda é uma descrição legível da falha. As outras linhas são ignoradas. | opcional |
TEST_LOGSPLITTER_OUTPUT_FILE |
caminho absoluto para um arquivo particular em um diretório gravável (usado para gravar um registro 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 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 run number
(começando com 1) para cada execução de teste individual. |
opcional |
TEST_TARGET |
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 tocar para indicar suporte ao sharding |
opcional |
TEST_SRCDIR |
caminho absoluto para a base da árvore de arquivos de execução | obrigatório |
TEST_TOTAL_SHARDS |
Total de
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 gravável particular (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 .part e .pb de anotação de saída de teste não declarados. |
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 do teste precisam gravar um arquivo de saída XML de resultados. Caso contrário, o Bazel gera um arquivo de saída XML padrão que encapsula o registro de teste como parte da ação de teste. O esquema XML é baseado no esquema do resultado do teste JUnit. | opcional |
BAZEL_TEST |
Significa que o executável do teste está sendo conduzido 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
.
Os IDs do processo atual, do grupo de processos, da sessão e do processo pai não são especificados. O processo pode ou não ser um líder de grupo de processo 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 recebe controle.
O descritor de arquivos 0 (stdin
) deve estar aberto para leitura, mas ao que ele está anexado
não foi especificado. Os testes não podem ler a partir dele. Os descritores de arquivos 1 (stdout
) e 2
(stderr
) vão estar abertos para gravação, mas ao que estão anexados não
é especificado. Pode ser um terminal, um pipe, um arquivo regular ou qualquer outra coisa em que os caracteres podem ser gravados. Eles podem compartilhar uma entrada na tabela de arquivos aberta,
ou seja, que não podem buscar de forma independente. Os testes não podem herdar nenhum
outro descritor de arquivo aberto.
O umask inicial precisa ser 022
ou 027
.
Nenhum alarme ou timer de intervalo deve estar pendente.
A máscara inicial de sinais bloqueados precisa estar vazia. Todos os indicadores vão ser definidos para a ação padrão.
Os limites iniciais de recursos, tanto flexíveis quanto rígidos, devem ser definidos da seguinte maneira:
Recurso | Limite |
---|---|
RLIMIT_AS |
unlimited |
RLIMIT_CORE |
não especificado |
RLIMIT_CPU |
unlimited |
RLIMIT_DATA |
unlimited |
RLIMIT_FSIZE |
unlimited |
RLIMIT_LOCKS |
unlimited |
RLIMIT_MEMLOCK |
unlimited |
RLIMIT_MSGQUEUE |
não especificado |
RLIMIT_NICE |
não especificado |
RLIMIT_NOFILE |
pelo menos 1.024 |
RLIMIT_NPROC |
não especificado |
RLIMIT_RSS |
unlimited |
RLIMIT_RTPRIO |
não especificado |
RLIMIT_SIGPENDING |
não especificado |
RLIMIT_STACK |
ilimitado, ou 2044KB <= rlim <= 8.192 KB |
Os tempos do processo inicial (conforme retornado por times()
) e a utilização de recursos
(como retornado por getrusage()
) não são especificados.
A política e a prioridade de programação inicial não são especificadas.
Papel do sistema host
Além dos aspectos do contexto do usuário sob controle direto do executor de teste, 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.
/proc
precisa ser montado.
Todas as ferramentas de build precisam estar presentes 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
será gravável, mas os testes precisam evitar o uso desses caminhos
Os testes não podem presumir que algum caminho constante esteja disponível para uso exclusivo.
Os testes não podem presumir que atimes estão habilitados para qualquer sistema de arquivos montado.
Usuários e grupos
Os usuários raiz, ninguém e teste de unidade precisam existir. Os grupos raiz, ninguém 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, da mesma forma que os IDs de grupos. Além disso, o ID do usuário, o ID do grupo, o nome do usuário e o nome do grupo atuais não são especificados. O conjunto de IDs de grupos complementares não é especificado.
Os IDs do usuário e do grupo atuais precisam ter nomes correspondentes que possam ser
recuperados com getpwuid()
e getgrgid()
. O mesmo pode não ser verdadeiro para
IDs de grupo complementares.
O usuário atual precisa ter um diretório principal. Isso pode não ser gravável. Os testes não podem tentar fazer gravações nele.
Rede
O nome do 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 nome do host cortado após o primeiro ponto também precisa funcionar. O localhost do nome do host 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 executará o teste. Se um teste usar fragmentação, cada fragmento individual reservará o número de núcleos de CPU especificado aqui.
Os testes podem criar subprocessos, mas não grupos ou sessões.
Há um limite no 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.
X As janelas podem estar disponíveis ou não. Os testes que precisam de um servidor X precisam iniciar a Xvfb.
Testar a interação com o sistema de arquivos
Todos os caminhos de arquivo especificados nas variáveis de ambiente de teste apontam para algum lugar no sistema de arquivos local, a menos que especificado de outra forma.
Os testes precisam criar arquivos somente nos diretórios especificados por
$TEST_TMPDIR
e $TEST_UNDECLARED_OUTPUTS_DIR
(se definidos).
Esses diretórios ficarão inicialmente vazios.
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,
limites de tamanho do caminho para soquetes de domínio Unix
geralmente exigem a criação do soquete em /tmp
. O Bazel não vai conseguir
rastrear esses arquivos. O teste em si precisa ter o cuidado de ser hermético, usar caminhos
exclusivos para evitar colisão com outros, executar testes simultaneamente e processos
que não são de teste, além de limpar os arquivos criados em /tmp
.
Alguns frameworks de teste conhecidos, como
JUnit4 TemporaryFolder
ou Go TempDir
, têm
as próprias maneiras de criar um diretório temporário em /tmp
. Esses frameworks
de teste incluem uma funcionalidade que limpa arquivos em /tmp
. Assim, você pode usá-los
mesmo que eles criem arquivos fora de TEST_TMPDIR
.
Os testes precisam acessar entradas por meio do mecanismo runfiles ou outras partes do ambiente de execução que se destinam especificamente a disponibilizar arquivos de entrada.
Os testes não podem acessar outras saídas do sistema de compilação em caminhos inferidos do local do próprio executável.
Não é especificado se a árvore de arquivos de execução contém arquivos regulares, links simbólicos
ou uma combinação. A árvore dos arquivos de execução pode conter links simbólicos para diretórios.
Os testes precisam evitar o uso de caminhos que contêm componentes ..
na árvore
de arquivos de execução.
Nenhum diretório, arquivo ou link simbólico na árvore de arquivos de execução (incluindo caminhos que
passam links simbólicos) deve ser gravável. (Segue que o diretório de trabalho inicial
não deve ser gravável.) Os testes não podem presumir que qualquer parte dos
runfiles seja gravável ou seja de propriedade do usuário atual (por exemplo, chmod
e chgrp
podem
falhar).
A árvore de arquivos de execução (incluindo caminhos que passam por links simbólicos) não pode ser alterada durante a execução do teste. Diretórios pai e montagens do sistema de arquivos não podem ser alterados de nenhuma maneira, o que afeta o resultado da resolução de um caminho na árvore de arquivos de execução.
Para capturar a saída antecipada, um teste pode criar um arquivo no caminho especificado por
TEST_PREMATURE_EXIT_FILE
no início e removê-lo ao sair. Se o Bazel encontrar o
arquivo quando o teste terminar, ele vai presumir que foi encerrado prematuramente e
marcar como falha.
Convenções de tags
Algumas tags nas regras de teste têm um significado especial. Consulte também a
enciclopédia de build do Bazel no atributo tags
(link em inglês).
Tag | Significado |
---|---|
exclusive |
não executar nenhum outro teste ao mesmo tempo |
external |
teste tem uma dependência externa; desative o armazenamento em cache do teste |
large |
Convenção test_suite ; pacote de testes grandes |
manual * |
não inclua destino de teste em padrões de destino de caractere curinga, como
:... , :* ou :all . |
medium |
Convenção test_suite ; pacote de testes médios |
small |
Convenção test_suite ; pacote de testes pequenos |
smoke |
convenção test_suite , ou seja, ela precisa ser executada antes de confirmar as mudanças de código no sistema de controle de versões. |
Arquivos de execução
A seguir, suponha que haja uma regra *_binary() rotulada
//foo/bar:unittest
, com uma dependência de tempo de execução na regra rotulada
//deps/server:server
.
Local
O diretório runfiles de uma //foo/bar:unittest
de destino é o diretório
$(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles
. Esse caminho é conhecido como
runfiles_dir
.
Dependências
O diretório runfiles é declarado como uma dependência de tempo de compilação da regra
*_binary()
. O próprio 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
execução. A modificação de arquivos de origem não afeta a estrutura do diretório
runfiles e, portanto, não aciona nenhuma recriação.
Conteúdo
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()
é representada 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 do 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 sub-runfiles: 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 runfiles. Por exemplo, todos os subprogramas compartilham um diretório runfiles comum.