Enciclopédia de teste

É uma especificação completa do ambiente de execução de testes.

Contexto

A linguagem Bazel BUILD 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. Sendo assim, uma invocação não aderirá às autorizações descritas abaixo.

Os testes precisam ser herméticos, ou seja, precisam acessar apenas os recursos em que têm uma dependência declarada. Se os testes não são adequadamente herméticos, eles não fornecem resultados historicamente reproduzíveis. Isso pode ser um problema significativo para a descoberta de culpados (determinar qual alteração quebrou um teste), liberar a auditoria de engenharia e isolamento de recursos de testes (frameworks de teste automatizados não devem usar 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. Isso também vai impor requisitos ao executor de testes e ao sistema de build.

A especificação do ambiente de teste ajuda os autores de testes a evitar depender de comportamentos não especificados e, portanto, dá à infraestrutura de teste mais liberdade para fazer alterações de implementação. A especificação aperta alguns buracos que atualmente permitem a aprovação de muitos testes, mesmo que não sejam adequadamente herméticos, deterministas e reentrantes.

O objetivo desta página é ser regulatório e autoritativo. 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 "PRECISA", "NÃO PODE", "OBRIGATÓRIO", "DEVERÁ", "NÃO DEVE", "DEVE", "NÃO DEVE", "RECOMENDADO", "PODE" e "OPCIONAL" devem ser interpretadas conforme descrito no IETF RFC 2119.

Finalidade dos testes

A finalidade dos testes do Bazel é confirmar algumas propriedades dos arquivos de origem registrados no repositório. Nesta página, "arquivos de origem" incluem dados de teste, saídas douradas e tudo o que for mantido sob controle de versão. Um usuário cria um teste para declarar uma invariante que espera ser mantida. Outros usuários executam 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 será reduzido, porque os usuários posteriores não terão certeza de que as mudanças são falhas quando o teste para de ser aprovado.

Portanto, o resultado de um teste depende 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 garantido pelo executor de testes que permaneçam constantes.

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 build

As regras de teste são análogas às regras binárias, porque cada uma delas precisa gerar um programa executável. Para algumas linguagens, esse é um programa stub que combina um arcabouço 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 de teste principal, o executor de testes precisará de um manifesto de runfiles, arquivos de entrada que serão 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 build pode usar os arquivos de execução para entregar código e dados. Isso pode ser usado como uma otimização para diminuir cada binário de teste compartilhando arquivos entre testes, como com o uso de vinculação dinâmica. O sistema de build precisa garantir que o executável gerado carregue esses arquivos usando a imagem dos arquivos de execução fornecidas pelo executor do teste, em vez de referências fixadas no código a locais absolutos na árvore de origem ou de saída.

Papel do executor de testes

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 no processo. No entanto, o resultado da execução do teste como um processo autônomo precisa ser considerado autoritativo. Se um processo de teste for executado até a conclusão e terminar normalmente com um código de saída igual a zero, 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 testes.

Se um teste demorar muito para ser executado, exceder algum limite de recursos ou o executor do teste detectar comportamentos proibidos, ele poderá encerrar o teste e tratar a execução como uma falha. O executor não pode informar que o teste foi aprovado após o envio de um sinal para o processo de teste ou qualquer filho dele.

Todo o destino do teste (não métodos ou testes individuais) recebe um tempo limitado para ser executado. O limite de tempo de um teste é baseado no atributo timeout de acordo com a tabela abaixo:

timeout Limite de tempo (s)
short 60
moderada 300
long 900
eterno 3.600

Os testes que não especificam um tempo limite explicitamente 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 moderada
grande long
enorme eterno

Um teste "grande" sem configuração de tempo limite explícito terá 900 segundos para ser executado. Um teste "médio" com um tempo limite "curto" é alocado para 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 "enorme" pode ser declarado com um tempo limite "curto". Provavelmente, ele faria algumas coisas horríveis muito rapidamente.

Os testes podem retornar arbitrariamente rápido, independentemente do tempo limite. Um teste não é penalizado por um tempo limite muito generoso, embora um aviso possa ser emitido. Geralmente, você precisa definir o tempo limite o máximo possível sem incorrer em erros.

O tempo limite do teste pode ser substituído pela sinalização do Bazel --test_timeout durante a execução manual em condições conhecidas por serem 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, como segue:

timeout Tempo mínimo (s)
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 mostrará os testes cujo tamanho especificado é muito grande.

Os tamanhos e tempos limite do teste são especificados no arquivo BUILD de acordo com esta especificação. Se não for especificado, o tamanho de um teste será padronizado como "médio".

Se o processo principal de um teste é encerrado, mas alguns dos filhos ainda estão em execução, o executor de testes precisa considerar a execução concluída e contá-la como sucesso ou falha com base no código de saída observado do processo principal. O executor de testes pode eliminar processos indesejados. Os testes não podem vazar processos dessa maneira.

Fragmentação de testes

Os testes podem ser carregados em paralelo com a 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 do fragmento, começando em 0. Os executores usam essas informações para selecionar quais testes serão executados, por exemplo, usando uma estratégia round-robin. Nem todos os executores de testes oferecem suporte à fragmentação. Se um executor oferecer suporte à 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 for fragmentado.

Condições iniciais

Ao realizar 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 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 mostrado abaixo. O executor de testes não pode transmitir nenhum outro argumento para um teste, a menos que o usuário o solicite explicitamente.

O bloco de ambiente inicial será composto da seguinte maneira:

Variável Valor Status
HOME valor de $TEST_TMPDIR recomendado
LANG unset de provisionamento.
LANGUAGE unset de provisionamento.
LC_ALL unset de provisionamento.
LC_COLLATE unset de provisionamento.
LC_CTYPE unset de provisionamento.
LC_MESSAGES unset de provisionamento.
LC_MONETARY unset de provisionamento.
LC_NUMERIC unset de provisionamento.
LC_TIME unset de provisionamento.
LD_LIBRARY_PATH lista de diretórios separada por dois-pontos contendo bibliotecas compartilhadas opcional
JAVA_RUNFILES valor de $TEST_SRCDIR descontinuado
LOGNAME valor de $USER de provisionamento.
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 instáveis de testes. Nesse contexto, a infraestrutura de testes é definida como sistemas ou bibliotecas que não são específicos para testes, mas que podem causar falhas no teste por mau 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 serã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 buffer de protocolo 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 alvo que está sendo testado. opcional
TEST_SIZE O size de teste 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 do arquivo a ser tocado para indicar suporte a sharding opcional
TEST_SRCDIR caminho absoluto para a base da árvore de arquivos de execução de provisionamento.
TEST_TOTAL_SHARDS total de shard count, se sharding for usado opcional
TEST_TMPDIR caminho absoluto para um diretório gravável particular de provisionamento.
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 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 declarado. 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 de provisionamento.
USER valor de getpwuid(getuid())->pw_name de provisionamento.
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 resultados de teste JUnit. opcional
BAZEL_TEST Indica que o executável de teste está sendo acionado por bazel test. de provisionamento.

O ambiente pode conter entradas adicionais. 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 códigos do processo atual, do grupo de processos, da sessão e do processo pai não foram especificados. O processo pode ou não ser um líder do grupo de processos ou da 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 poderá ter várias linhas de execução quando o código de teste ganhar o controle.

O descritor do arquivo 0 (stdin) será aberto para leitura, mas não será especificado ao qual ele está anexado. Os testes não podem ler esses dados. Os descritores de arquivo 1 (stdout) e 2 (stderr) vão estar abertos para gravação, mas eles não vão estar especificados. Pode ser um terminal, um pipe, um arquivo regular ou qualquer outra coisa em que os caracteres possam ser gravados. Eles podem compartilhar uma entrada na tabela de arquivos abertos, o que significa que não podem buscar independentemente. 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 dos sinais bloqueados precisa estar vazia. Todos os indicadores precisam ser definidos para a ação padrão.

Os limites de recursos iniciais, flexíveis e rígidos, precisam ser definidos da seguinte maneira:

Resource 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 No mínimo 1024
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 processamento iniciais (como retornados por times()) e a utilização de recursos (como retornados por getrusage()) não estão especificados.

A prioridade e a política de programação inicial não foram 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 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 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 precisa ser gravável, mas os testes precisam evitar o uso desses caminhos.

Os testes não podem presumir que algum caminho constante está disponível para uso exclusivo.

Os testes não podem assumir que os horários estão ativados para qualquer sistema de arquivos montado.

Usuários e grupos

A raiz de usuários, ninguém e o unittest precisam existir. A raiz dos grupos, 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 efetivos precisam ser iguais, assim como para IDs de grupo. Além disso, os IDs do usuário, do grupo, do usuário e do grupo atuais não são especificados. O conjunto de IDs de grupos suplementares não é especificado.

Os IDs 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 complementares.

O usuário atual precisa ter um diretório principal. Pode não ser gravável. Os testes não podem tentar gravar nele.

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. A solução do corte do nome do host após o primeiro ponto também precisa funcionar. O localhost precisa ser resolvido pelo nome do host.

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 foram especificados. Aumente 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 um total de núcleos de CPU menor do que o solicitado, o Bazel ainda executará o teste. Se um teste usar a fragmentação, cada fragmento individual reservará o número de núcleos de CPU especificado aqui.

Os testes podem criar subprocessos, mas não 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 mudanças, 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 foi especificado.

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

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

Esses diretórios estarão vazios inicialmente.

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/. 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 consegue rastrear esses arquivos. O teste em si precisa ser hermético, usar caminhos exclusivos para evitar colisão com outros testes que executam testes e processos que não são de teste simultaneamente e limpe 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. Portanto, 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 destinadas especificamente a 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 dos 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 passam por links simbólicos) precisa 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 é gravável ou pertence ao usuário atual (por exemplo, chmod e chgrp podem falhar).

A árvore dos arquivos de execução (incluindo caminhos que passam por links simbólicos) não pode mudar durante a execução do teste. Os diretórios pais e as montagens do sistema de arquivos não podem ser alterados de maneira alguma, afetando o resultado da resolução de um caminho na árvore de arquivos de execução.

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 vir o arquivo quando o teste for concluído, ele vai presumir que o teste foi encerrado prematuramente e marcá-lo como falha.

Convenções de tag

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

Tag Significado
exclusive executar nenhum 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 inclua 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; pacote de testes pequenos
smoke test_suite, que significa que ele precisa ser executado antes da confirmação das mudanças do código no sistema de controle de versões.

Arquivos de execução

A seguir, suponha que há uma regra *_binary() rotulada como //foo/bar:unittest, com uma dependência de ambiente de execução na regra //deps/server:server.

Local

O diretório runfiles de 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 runfiles é declarado como uma dependência de 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 compilação ou execução. A modificação de arquivos de origem não afeta a estrutura do diretório do runfiles e, portanto, não aciona a 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 ambiente 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 é 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 de arquivos de execução comum.