Uma especificação completa do ambiente de execução de teste.
Contexto
A linguagem Bazel BUILD inclui regras que podem ser usadas para definir programas de teste automatizados em muitas 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 recomendado, porque a invocação não atenderá às autorizações descritas abaixo.
Os testes precisam ser herméticos, ou seja, eles precisam acessar apenas os recursos em que têm uma dependência declarada. Se os testes não forem herméticos de maneira adequada, eles não fornecerão resultados historicamente reproduzíveis. Isso pode ser um problema significativo para a descoberta de culpados (determinar qual mudança quebrou um teste), auditabilidade de engenharia de lançamento e isolamento de recursos dos testes (frameworks de teste automatizados não devem usar o DDOS em um servidor porque alguns testes se comunicam com ele).
Objetivo
O objetivo desta página é estabelecer formalmente o ambiente de tempo de execução e o comportamento esperado dos testes do Bazel. Ela também impõe requisitos no executor de testes e no sistema de compilação.
A especificação do ambiente de teste ajuda os autores de testes a evitar confiar em comportamentos não especificados e, portanto, oferece à infraestrutura de testes mais liberdade para fazer alterações de implementação. A especificação reduz alguns buracos que, no momento, permitem que muitos testes sejam aprovados, apesar de não serem herméticas, determinísticas e reentrantes corretamente.
Esta página tem o objetivo de ser regulatória 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 no IETF RFC 2119.
Finalidade dos testes
O objetivo dos testes do Bazel é confirmar se as propriedades dos arquivos de origem foram verificadas no repositório. Nesta página, "arquivos de origem" inclui dados de teste, saídas douradas e qualquer outra coisa mantida no controle de versão. Um usuário escreve um teste para declarar uma invariante que espera ser mantida. Outros usuários realizam o teste mais tarde para verificar se a invariante foi corrompida. Se o teste depender de variáveis diferentes dos arquivos de origem (não herméticos), o valor vai ser reduzido, porque os usuários posteriores não vão ter certeza de que as mudanças vão estar com falhas quando o teste parar de ser aprovado.
Portanto, o resultado de um teste precisa depender apenas de:
- arquivos de origem em que o teste tem uma dependência declarada;
- produtos do sistema de compilação em que o teste tem uma dependência declarada
- recursos cujo comportamento é garantido pelo executor de teste para permanecer constante
Atualmente, esse comportamento não é aplicado. No entanto, os executores de testes reservam o direito de adicionar essa aplicação no futuro.
Papel do sistema de compilação
As regras de teste são análogas às regras binárias, em que cada uma precisa produzir um programa executável. Para algumas linguagens, esse é um programa de stub que combina um arcabouço específico com o código de teste. As regras de teste também precisam produzir outras saídas. Além do executável de teste principal, o executor de testes precisará de um manifesto de runfiles, arquivos de entrada que precisam ser disponibilizados para o teste no momento da 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ódigo e dados. Isso pode ser usado como uma otimização para tornar cada teste binário menor, compartilhando arquivos entre os testes, por exemplo, por meio do uso de links dinâmicos. O sistema de compilação precisa garantir que o executável gerado carregue esses arquivos por meio da imagem de arquivos de execução fornecida pelo executor de testes, em vez de referências fixadas no código para locais absolutos na árvore de origem ou de saída.
Papel do executor de testes
Do ponto de vista do executor, 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 em processo. No entanto, o resultado da execução do teste como um processo independente precisa ser considerado autoritativo. Se
um processo de teste for executado até o fim e terminar normalmente com um código de saída
zero, isso significa que o teste foi 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 teste.
Se a execução de um teste demorar muito, exceder algum limite de recursos ou detectar um comportamento proibido, o executor 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 para qualquer filho dele.
Todo o destino de teste (não métodos ou testes individuais) recebe um período limitado para ser executado. O limite de tempo para 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 |
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 de tempo limite explícito será alocado para execução em 900 segundos. Um teste "médio" com um tempo limite de "curto" será alocado por 60 segundos.
Ao contrário de timeout
, o size
também determina o pico de uso 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 "grande"
pode ser declarado como tendo um tempo limite "curto". Certamente, isso
faria coisas muito horríveis muito rapidamente.
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, você deve definir seu tempo limite o mais restrito possível, sem causar instabilidade.
O tempo limite do teste pode ser substituído pela sinalização do 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:
pedido de tempo | Mínimo de tempo (s) |
---|---|
short | 0 |
moderado | 30 |
long | 300 |
eterno | 900 |
Por exemplo, se um teste "moderado" for concluído em 5,5 segundos, considere configurar timeout =
"short"
ou size = "small"
. Usar a opção de linha de comando --test_verbose_timeout_warnings
do Bazel mostrará os testes cujo tamanho especificado é muito grande.
Os tamanhos e tempos limite de teste são especificados no arquivo BUILD de acordo com as especificações disponíveis aqui. Se não for especificado, o tamanho do teste será padronizado como "médio".
Se o processo principal de um teste for encerrado, mas alguns filhos ainda estiverem em execução, o executor precisará considerar a execução concluída e contá-la como um sucesso ou falha com base no código de saída observado no processo principal. O executor de testes pode eliminar processos perdidos. Os testes não podem vazar processos dessa maneira.
Fragmentação de testes
Os testes podem ser carregados em paralelo com fragmentação de testes. Consulte
--test_sharding_strategy
e shard_count
para
ativar a fragmentação de testes. Quando a fragmentação é 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 do fragmento, começando em 0. Os executores usam essas informações para selecionar quais testes executar, por exemplo, usando uma estratégia round-robin. Nem todos os executores de teste são compatíveis com a fragmentação. Se um executor for compatível com fragmentação, ele precisará criar ou atualizar a última data modificada do arquivo especificado por TEST_SHARD_STATUS_FILE
. Caso contrário, se --incompatible_check_sharding_support
estiver ativado, o Bazel 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 teste precisa invocar cada teste com o caminho para o executável em argv[0]
. Esse caminho precisa ser relativo e estar abaixo do diretório atual do teste (que está na árvore dos arquivos de execução, veja abaixo). O executor não pode passar
outros argumentos para um teste, a menos que o usuário o solicite explicitamente.
O bloco do 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 dos diretórios que contêm bibliotecas compartilhadas | opcional |
JAVA_RUNFILES |
valor de $TEST_SRCDIR |
obsoleto |
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 privado em um diretório gravável. Esse arquivo só deve ser usado para relatar falhas originadas da infraestrutura de teste, não como um mecanismo geral para relatar falhas irregulares de testes. Nesse contexto, a infraestrutura de teste é definida como sistemas ou bibliotecas que não são específicos do teste, mas podem causar falhas no funcionamento. A primeira linha é o nome do componente da infraestrutura de teste que causou a falha. A segunda é uma descrição legível da falha. As linhas adicionais 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 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 |
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 para sharding |
opcional |
TEST_SRCDIR |
caminho absoluto para a base da árvore de arquivos de execução | obrigatório |
TEST_TOTAL_SHARDS |
shard count no total, se sharding for usado |
opcional |
TEST_TMPDIR |
caminho absoluto para um diretório gravável particular | 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 gravável particular (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 precisam gravar um arquivo de saída XML do resultado do teste. Caso contrário, o Bazel gera um arquivo de saída XML padrão que envolve o registro de teste como parte da ação de teste. O esquema XML é baseado no esquema de resultado de teste JUnit. | opcional |
BAZEL_TEST |
Indica que o executável do teste está sendo acionado por bazel test . |
obrigatório |
O ambiente pode conter entradas adicionais. Os testes não podem depender da presença, da ausência ou do valor de qualquer variável de ambiente não listada acima.
O diretório de trabalho inicial será $TEST_SRCDIR/$TEST_WORKSPACE
.
O código do processo atual, o código do grupo de processos, o código da sessão e o código 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. 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
) estará aberto para leitura, mas ao que ele está anexado
não está especificado. Os testes não podem ler a partir dele. Os descritores de arquivo 1 (stdout
) e 2
(stderr
) precisam estar abertos para gravação, mas o conteúdo deles não está
especificado. Pode ser um terminal, uma barra vertical, um arquivo regular ou qualquer outra coisa em que os caracteres podem ser gravados. Eles podem compartilhar uma entrada na tabela de arquivos aberta, o que significa que eles não podem procurar de forma independente. Os testes não podem herdar outros descritores de arquivos abertos.
O Umask inicial precisa ser 022
ou 027
.
Nenhum alarme ou timer de intervalo deve estar pendente.
A máscara inicial dos indicadores bloqueados estará vazia. Todos os indicadores serão definidos com a ação padrão deles.
Os limites de recursos iniciais, flexíveis e 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 2.044 KB <= rlim <= 8.192 KB |
Os tempos de processo iniciais (conforme retornados por times()
) e a utilização de recursos (conforme retornados por getrusage()
) não são especificados.
A política de programação inicial e a prioridade não são especificadas.
Papel 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 satisfazer 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
será montado.
Todas as ferramentas de criação 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 devem evitar o uso desses caminhos.
Os testes não podem pressupor que qualquer caminho constante esteja disponível para uso exclusivo.
Os testes não podem presumir que o tempo de ativação esteja ativado para qualquer sistema de arquivos montado.
Usuários e grupos
Os usuários raiz, ninguém e o unittest devem existir. Os grupos raiz, ninguém e eng devem existir.
Os testes precisam ser executados como um usuário não raiz. Os IDs de usuário reais e eficazes precisam ser iguais. Da mesma forma, os IDs de grupo. Além disso, os códigos de usuário, de grupo e de grupo não são especificados. O conjunto de IDs de grupos complementares não é especificado.
Os IDs de usuário e de grupo atuais precisam ter nomes correspondentes que possam ser recuperados com getpwuid()
e getgrgid()
. O mesmo pode não ser verdade para IDs de grupos complementares.
O usuário atual precisa ter um diretório principal. Pode não ser gravável. Os testes não podem tentar gravar neles.
Rede
O nome do host não foi especificado. Ele pode ou não conter um ponto. Resolver o 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 nome do host localhost precisa ser resolvido.
Outros recursos
Os testes recebem pelo menos um núcleo da 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 totais de CPU 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 especificados aqui.
Os testes podem criar subprocessos, mas não podem processar grupos ou sessões.
Há um limite no número de arquivos de entrada que um teste pode consumir. Esse limite está sujeito a alterações, mas atualmente está no intervalo 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 está especificado.
O Windows X pode não estar disponível. Os testes que precisam de um servidor X precisam 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 um 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 estarão inicialmente vazios.
Os testes não podem tentar remover, alterar ou alterar esses diretórios.
Esses diretórios podem ser links simbólicos.
O tipo de sistema de arquivos $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 tamanho de caminho para soquetes de domínio Unix normalmente exigem a criação do soquete em /tmp
. O Bazel não conseguirá rastrear esses arquivos. O teste precisa ter cuidado para ser hermético, usar caminhos únicos para evitar colisão com outros, executar testes e processos que não sejam de teste simultaneamente e 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 funcionalidades que limpam arquivos em /tmp
, então você pode usá-los mesmo que eles criem arquivos fora de TEST_TMPDIR
.
Os testes precisam acessar entradas por meio do mecanismo runfiles ou de 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 da localização do próprio executável.
Não especifica se a árvore de arquivos de execução contém arquivos regulares, links simbólicos ou uma mistura. A árvore dos arquivos de execução pode conter links simbólicos para os 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 dos arquivos de execução (incluindo caminhos que
passam links simbólicos) não pode ser gravado. O diretório de trabalho inicial não deve ser gravável. Os testes não podem presumir que qualquer parte dos
runruns seja gravável ou que pertença ao usuário atual. Por exemplo, chmod
e chgrp
podem
falhar.
A árvore de arquivos de execução (incluindo caminhos que atravessam symlinks) não pode ser alterada durante a execução do teste. Os diretórios pai e as ativações do sistema de arquivos não podem mudar de forma que afetem 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 na saída. Se o Bazel vir o arquivo quando o teste for concluído, ele assumirá que o teste foi encerrado prematuramente e o marcará como falha.
Convenções de tag
Algumas tags nas regras de teste têm um significado especial. Consulte também a Enciclopédia de compilação do Bazel no atributo tags
.
Tag | Significado |
---|---|
exclusive |
não executar outro teste ao mesmo tempo |
external |
teste tem uma dependência externa; desativar o armazenamento em cache de teste |
large |
Convenção test_suite ; conjunto de testes grandes |
manual * |
não inclua destino de teste em padrões de destino de caracteres curinga, como :... , :* ou :all |
medium |
Convenção do test_suite ; conjunto de testes médios |
small |
Convenção test_suite ; pacote de testes pequenos |
smoke |
test_suite . Significa que ela precisa ser executada antes da confirmação de mudanças de código no sistema de controle de versão. |
Arquivos de execução
No exemplo a seguir, suponha que há uma regra *_binary() denominada //foo/bar:unittest
, com uma dependência de ambiente de execução na regra denominada //deps/server:server
.
Local
O diretório de arquivos de execução para um //foo/bar:unittest
de destino é o diretório
$(WORKSPACE)/$(BINDIR)/foo/bar/unittest.runfiles
. Esse caminho é chamado de runfiles_dir
.
Dependências
O diretório de arquivos de execução é declarado como uma dependência em tempo de compilação da
regra *_binary()
. O diretório runfiles em si depende do conjunto de arquivos BUILD
que afetam a regra *_binary()
ou qualquer uma das dependências de tempo de execução
ou execução. A modificação de arquivos de origem não afeta a estrutura do diretório de arquivos de execução e, portanto, não aciona nenhuma recriação.
Índice
O diretório runfiles contém o seguinte:
- Links simbólicos para dependências de ambiente de execução: cada OutputFile e CommandRule que é uma dependência de tempo de execução da regra
*_binary()
são representados por um link simbólico no diretório de arquivos de execução. O nome do link simbólico é$(WORKSPACE)/package_name/rule_name
. Por exemplo, o link simbólico para o servidor seria denominado$(WORKSPACE)/deps/server/server
e o caminho completo seria$(WORKSPACE)/foo/bar/unittest.runfiles/$(WORKSPACE)/deps/server/server
. O destino do symlink é o OutputFileName() do OutputFile ou CommandRule, expresso como um caminho absoluto. Assim, o destino do symlink 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 symlink é o diretório runfiles. Por exemplo, todos os subprogramas compartilham um diretório de arquivos de execução comuns.