Enciclopédia de teste

Reportar um problema Ver código-fonte Nightly · 8.0 . 7.4 . 7.3 · 7.2 · 7.1 · 7.0 · 6.5

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á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, eles precisam acessar apenas os recursos em que têm uma dependência declarada. Se os testes não forem herméticos corretamente, eles não vão gerar resultados reprodutíveis historicamente. Isso pode ser um problema significativo para a identificação do culpado (determinar qual mudança quebrou um teste), a auditabilidade da engenharia de lançamento e o isolamento de recursos dos testes (bibliotecas de teste automatizadas não devem fazer DDOS em 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 teste e ao sistema de build.

A especificação do ambiente de teste ajuda os autores 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 aperta alguns furos que atualmente permitem que muitos testes sejam aprovados, apesar de não serem herméticos, determinísticos e reentrantes.

Esta página tem o objetivo de ser normativa e autorizada. Se essa especificação e o comportamento implementado do executor de teste forem diferentes, 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" serão interpretadas conforme descrito no IETF RFC 2119.

Finalidade dos testes

O objetivo dos testes do Bazel é confirmar algumas propriedades dos arquivos de origem verificados no repositório. Nesta página, "arquivos de origem" inclui dados de teste, saídas de ouro e qualquer outra coisa mantida sob controle de versão. Um usuário escreve um teste para declarar uma invariante que ele espera que seja mantida. Outros usuários executam o teste mais tarde para verificar se a invariante foi violada. Se o teste depender de variáveis diferentes dos arquivos de origem (não herméticos), o valor será diminuído, porque os usuários posteriores não poderão ter certeza de que as mudanças são a causa do problema quando o teste deixa de ser aprovado.

Portanto, o resultado de um teste só pode depender 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 teste 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, em que cada uma precisa gerar um programa executável. Para algumas linguagens, esse é um programa stub que combina um harness específico da linguagem com o código de teste. As regras de teste também precisam produzir outras saídas. Além do executável do teste principal, o executor de teste precisa de um manifesto de runfiles, arquivos de entrada que precisam ser disponibilizados para o teste no momento da 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 fornecer código e dados. Isso pode ser usado como uma otimização para tornar cada binário de teste menor ao compartilhar arquivos entre testes, como pelo uso de vinculação dinâmica. O sistema de build precisa garantir que o executável gerado carregue esses arquivos pela imagem de runfiles fornecida pelo executor de teste, em vez de referências fixadas em código a locais absolutos na árvore de origem ou de saída.

Função do executor de teste

Do ponto de vista do executor de teste, 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 confiável. Se um processo de teste for concluído e encerrado normalmente com um código de saída zero, o teste será aprovado. Qualquer outro resultado é considerado uma falha no teste. Especificamente, escrever qualquer uma das strings PASS ou FAIL no stdout não tem importância para o executor de teste.

Se um teste demorar muito para ser executado, exceder algum limite de recursos ou se o executor de teste 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 para qualquer elemento filho dele.

O alvo do teste inteiro (não métodos ou testes individuais) recebe um tempo limitado para ser concluído. 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édio moderada
grande long
enorme eterno

Um teste "grande" sem uma configuração de tempo limite explícita vai receber 900 segundos para ser executado. Um teste "médio" com um tempo limite de "curto" vai receber 60 segundos.

Ao contrário do 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 de "curto". Provavelmente, ele faria coisas realmente horríveis muito rapidamente.

Os testes podem ser retornados de forma arbitrária, independentemente do tempo limite. Um teste não é penalizado por um tempo limite excessivo, embora um aviso possa ser emitido: geralmente, você deve definir o tempo limite o mais curto possível sem causar instabilidade.

O tempo limite do teste pode ser substituído com a flag --test_timeout do Bazel ao ser executado manualmente em condições que são conhecidas por serem lentas. Os valores de --test_timeout são em segundos. Por exemplo, --test_timeout=120 define o tempo limite do teste como dois minutos.

Também há um limite mínimo recomendado para os tempos limite de teste:

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 vai mostrar os testes cujo tamanho especificado é muito grande.

Os 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á "médio" por padrão.

Se o processo principal de um teste for encerrado, mas alguns dos filhos ainda estiverem em execução, o executor de teste vai considerar a execução completa e a contabilizar como uma falha ou sucesso com base no código de saída observado no processo principal. O executor de teste pode encerrar qualquer processo perdido. Os testes não podem vazar processos dessa maneira.

Testar a fragmentação

Os testes podem ser paralelos usando a fragmentação de testes. Consulte --test_sharding_strategy e shard_count para ativar o 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 de fragmentos, começando em 0. Os executores usam essas informações para selecionar quais testes executar, por exemplo, usando uma estratégia de round-robin. Nem todos os executores de teste oferecem suporte ao fragmentação. Se um executor tiver 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 estiver 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 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, consulte abaixo). O executor de teste não pode transmitir outros argumentos para 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 de diretórios contendo bibliotecas compartilhadas separada por dois-pontos 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 pela 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íficos do teste, 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 por humanos 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 de protobuffer do Logsplitter) opcional
TEST_PREMATURE_EXIT_FILE caminho absoluto para um arquivo particular em um diretório gravável (usado para detectar 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 fragmentos, 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 total shard count, 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 .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 de teste precisam gravar um arquivo de saída XML de resultado de 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 do JUnit. opcional
BAZEL_TEST Indica que o executável de teste está sendo controlado 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 nenhuma 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 foram especificados. O processo pode ou não ser um líder de grupo de processos ou um líder 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 assume o controle.

O descritor de arquivo 0 (stdin) será aberto para leitura, mas o que ele está anexado não é especificado. Os testes não podem ler esse arquivo. Os descritores de arquivos 1 (stdout) e 2 (stderr) precisam estar abertos para gravação, mas o que eles estão anexados não é especificado. Pode ser um terminal, um tubo, um arquivo normal ou qualquer outra coisa em que os caracteres possam ser escritos. Eles podem compartilhar uma entrada na tabela de arquivos abertos, o que significa que não podem pesquisar 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 pode estar pendente.

A máscara inicial de indicadores bloqueados precisa estar vazia. Todos os sinais serão 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 1.024
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 de programação inicial e a prioridade 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 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.

O /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.

O /tmp precisa ser gravável, mas os testes devem evitar o uso desses caminhos.

Os testes não podem presumir que qualquer caminho constante esteja 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. Os grupos root, 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, 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 atual precisam ter nomes correspondentes que podem ser recuperados com getpwuid() e getgrgid(). O mesmo não se aplica a IDs de grupo 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 do host não foi especificado. Ela pode ou não conter um ponto. A resolução do nome do host precisa fornecer um endereço IP do host atual. A resolução do nome do host cortado após o primeiro ponto também precisa funcionar. O nome de host localhost precisa ser resolvido.

Outros recursos

Os testes são concedidos com 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 usa fragmentação, cada fragmento individual reserva o número de núcleos de CPU especificados aqui.

Os testes podem criar subprocessos, mas não processar grupos 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 data e a hora atuais não são especificadas. O fuso horário do sistema não foi especificado.

O Windows X pode ou 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 nas variáveis do ambiente de teste apontam para algum lugar no sistema de arquivos local, a menos que seja especificado de outra forma.

Os testes devem criar arquivos apenas nos diretórios especificados por $TEST_TMPDIR e $TEST_UNDECLARED_OUTPUTS_DIR (se definido).

Esses diretórios vão estar vazios no início.

Os testes não podem tentar remover, usar chmod ou alterar esses diretórios.

Esses diretórios podem ser links simbólicos.

O tipo de sistema de arquivos de $TEST_TMPDIR/. não foi 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 comprimento de caminho para soquetes de domínio Unix normalmente exigem a criação do soquete em /tmp. O Bazel não vai conseguir rastrear esses arquivos. O teste em si precisa ser hermético, usar caminhos exclusivos para evitar colisões com outros testes e processos não de teste executados simultaneamente e limpar os arquivos criados em /tmp.

Alguns frameworks de teste conhecidos, como JUnit4 TemporaryFolder ou Go TempDir, têm seus próprios métodos para 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 as entradas pelo mecanismo de runfiles ou outras partes do ambiente de execução que têm a finalidade específica de disponibilizar arquivos de entrada.

Os testes não podem acessar outras saídas do sistema de build 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 mistura. A árvore de arquivos de execução pode conter links simbólicos para diretórios. Os testes precisam evitar o uso de caminhos que contenham 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 atravessam links simbólicos) pode ser gravável. O diretório de trabalho inicial não pode 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 atravessam links simbólicos) não pode mudar durante a execução do teste. Os diretórios e as montagens de sistema de arquivos pai não podem mudar de forma alguma, o que afeta o resultado da resolução de um caminho na árvore de runfiles.

Para detectar a saída antecipada, um teste pode criar um arquivo no caminho especificado por TEST_PREMATURE_EXIT_FILE na inicialização e removê-lo na saída. 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 tags

Algumas tags nas regras de teste têm um significado especial. Consulte também a Enciclopédia de build do Bazel sobre o atributo tags.

Tag Significado
exclusive não execute outro teste ao mesmo tempo
external O 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 incluir 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 pequenos testes
smoke Convenção test_suite: significa que ela precisa ser executada antes de confirmar as mudanças de código no sistema de controle de versões

Arquivos de execução

No exemplo a seguir, suponha que haja uma regra *_binary() com o rótulo //foo/bar:unittest, com uma dependência de tempo de execução na regra com o rótulo //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 é conhecido como runfiles_dir.

Dependências

O diretório de arquivos de execução é declarado como uma dependência do tempo de compilação da regra *_binary(). O próprio diretório de arquivos de execução depende do conjunto de arquivos BUILD que afetam a regra *_binary() ou qualquer uma das dependências de tempo de execução ou de compilação. Modificar arquivos de origem não afeta a estrutura do diretório de arquivos de execução e, portanto, não aciona nenhuma recriação.

Conteúdo

O diretório de arquivos de execução 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 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 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 seja uma dependência 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 de arquivos de execução comum.