Ir para o conteúdo

Blogue ·

Verde porque não conseguia lá chegar

O nosso teste de contrato respondia ao comando mais banal que existe com «não suportado». Todos os testes que dele dependiam estavam verdes pela razão errada.

Por Provibr

Mantemos um agente de imitação. Fala o mesmo protocolo que o verdadeiro, corre ao lado do gateway, e existe para que o lado de plataforma de cada comando possa ser testado sem tocar no hipervisor de um cliente. É um teste de contrato com um processo à volta.

Até esta semana esse agente de imitação respondia ao comando mais banal que existe, desligar aquela máquina, com COMMAND_STATUS_UNSUPPORTED.

Ninguém tinha reparado, e a razão por que ninguém tinha reparado é o ponto deste artigo. Todos os testes que enviavam um comando de energia ao agente de imitação estavam verdes. Estavam verdes porque o comando era recusado, a plataforma tratava a recusa corretamente, e a asserção do fim aguentava. Os testes não estavam a mentir. Estavam a descrever um caminho que ninguém tencionava exercitar.

O que um ramo em falta faz aos testes à sua volta

O motor de fluxos tem um nó de ação: executar um comando contra um servidor e seguir depois o fio ok ou o fio error, conforme tenha corrido. Esse nó estava no produto desde a ronda que o introduziu, e tinha testes.

Esses testes nunca conseguiam alcançar mais do que o fio error. Não por terem um defeito, mas porque o único agente à sua disposição recusava todos os comandos que eles podiam enviar. Um teste que afirma «foi tomado o ramo de erro» passa perfeitamente bem quando o erro foi fabricado pelo arnês de teste e não pela coisa que está a ser testada.

Esta é a forma contra a qual as nossas próprias notas avisam, numa frase que está no manual do projeto há meses: um teste pode estar verde porque não consegue chegar ao caminho bom. A variante que aqui nos mordeu é pior do que a habitual, porque não é um caso de um ramo que nunca foi escrito. O ramo foi escrito, funciona, e teve sempre ao lado um teste que passava.

O agente de imitação tinha de guardar estado e não apenas responder

Acrescentar os cinco comandos de energia não era uma questão de devolver sucesso. A plataforma decide o que um comando pode fazer lendo o estado atual do servidor: não se pode retomar uma máquina que está a correr, não se pode arrancar uma que já está de pé. Essa decisão vive na plataforma, e lê uma coluna.

Um agente que responde «feito» sem alterar nada deixa essa coluna onde estava. Portanto um teste consegue enviar exatamente um comando e fica depois encalhado, porque o segundo comando é recusado pela própria máquina de estados da plataforma, corretamente, sobre um estado que nunca se mexeu.

O agente de imitação passou por isso a guardar um estado de energia por máquina e a devolver a observação de estado pela sessão, tal como um agente a sério faz. Era isso que faltava de facto. Não uma resposta, uma consequência.

Com isso no lugar, um fluxo que percorre uma lista em ciclo e emite dois comandos por passagem pôde ser executado contra o agente de imitação pela primeira vez. Seis comandos, todos a chegar a concluído, e um ciclo que chegou à sua segunda passagem porque a primeira passagem tinha realmente alterado alguma coisa.

Duas coisas que o agente de imitação deliberadamente não faz

Não recusa transições impossíveis com um código de erro inventado. A fronteira entre o permitido e o não permitido vive na plataforma, e os códigos de motivo do agente são uma lista fixa integrada no código-fonte do agente a sério. Inventar um código que o agente a sério não tem seria testar um contrato que não existe. Uma imitação mais estrita do que aquilo que substitui ensina coisas que não são verdade.

Também não simula duração. Um encerramento numa máquina convidada a sério espera que o sistema operativo responda e recorre a uma paragem forçada ao fim de um tempo limite. Na imitação tudo acontece dentro do ciclo. Mas o tempo limite e o sinalizador de recurso são transportados até à saída, para que um teste possa ver que chegaram ainda que ninguém tenha esperado por eles. Transportar um parâmetro e honrá-lo são afirmações diferentes, e só uma delas está a ser feita.

Um segundo despertador torna aleatória uma medição

A outra reparação da mesma ronda é sobre a medição e não sobre o produto.

As execuções em pausa são acordadas por um trabalhador no gateway que sonda uma fila a cada dez segundos. O nosso script de ponta a ponta faz ele próprio o despertar, dentro do processo, para poder afirmar exatamente o que aconteceu. Os dois estão bem em separado e são catastróficos em conjunto: se um gateway com o despertador ativo estiver a correr contra a mesma base de dados de desenvolvimento, come as linhas assim que o momento de despertar passa, e o script mede aquele dos dois que lá chegou primeiro.

Foi o que aconteceu. Um ramo do script falhou uma vez sem ter nada de errado, que é o género de falha mais caro, porque a resposta natural é ir à procura de um defeito.

A correção não é um comentário a dizer «certifique-se de que não há nenhum trabalhador a correr». É uma função que o mede. Antes de tudo o resto, o script cria uma espera descartável com um momento de despertar já passado, observa-a durante oito segundos, e verifica se mais alguém lhe tocou. Se alguém tocou, o script recusa-se a correr e diz porquê.

É pouco código para muita confiança. Um pressuposto escrito num comentário é um pressuposto; o mesmo pressuposto escrito como uma verificação que corre primeiro é uma pré-condição.

As repetições precisam de um teto e de uma pausa crescente

Já que os caminhos bons estavam finalmente ao alcance, também os caminhos de falha foram medidos como deve ser.

Uma espera que rebenta ao retomar, porque um modelo foi eliminado ou um servidor já não existe, seria de outro modo repetida em todos os ciclos para sempre, numa tabela de fila que nunca se esvazia. Há um teto de cinco tentativas com uma pausa que duplica: sessenta, cento e vinte, duzentos e quarenta, quatrocentos e oitenta segundos. Esses quatro intervalos foram lidos de uma execução a sério e não da função que os calcula. À quinta tentativa a espera é cancelada e a execução é marcada como falhada.

A contraprova é a parte que interessa. Com a guarda de esgotamento desligada, a mesma execução fica no estado de espera indefinidamente. Uma guarda só está provada quando a situação que ela evita foi realmente produzida.

Há por baixo disso um segundo modo de falha. Um trabalhador reivindica uma espera tirando-a do estado de espera antes de começar a percorrer, porque os efeitos secundários de um fluxo acontecem durante a travessia e não depois dela. Se o processo que a reivindicou morrer a meio da travessia, a linha fica no estado de reivindicação para sempre, e a consulta que procura esperas vencidas não a vê: ela pede linhas em espera. Uma varredura repõe as reivindicações com mais de quinze minutos, e um único ciclo contra um gateway realmente a correr libertou uma, acordou uma e retomou uma, sem falhas.

Pequenas coisas que só se encontram executando-as

Um atraso de três segundos não produz um nó inválido. Torna inválido o grafo inteiro, porque o mínimo é cinco, e o executor não devolve absolutamente nada em vez de uma execução com um nó avariado. Isso está correto e é também confuso à primeira, porque se esperava um erro sobre um nó e obteve-se silêncio sobre um grafo.

As portas do gateway têm nomes que começam pelo gateway. Definir as variáveis sem esse prefixo deixa-o à escuta nos seus valores por omissão, em silêncio, o que se parece exatamente com um problema de rede.

Uma interrupção escreve no registo, como saltados, os nós a que nunca chegou. É uma diferença deliberada em relação a deixá-los de fora: uma execução que terminou cedo e uma execução que seguiu outro caminho parecem idênticas se os nós não alcançados estiverem simplesmente ausentes.

E um nó de aprovação estava a deixar linhas órfãs na tabela de notificações, dezasseis delas na base de dados de desenvolvimento. A limpeza disso é por execução e assenta na chave de desduplicação, nunca numa correspondência de padrão sobre um prefixo, porque uma correspondência de padrão levaria à frente linhas pertencentes à ronda de outra pessoa.

O que registaríamos

  1. Um teste que não consegue chegar ao caminho bom está verde pela razão errada. Pergunte o que faria o teste se a funcionalidade fosse eliminada.
  2. Uma imitação que recusa tudo não é uma imitação, é uma parede. Todos os testes atrás dela medem a recusa.
  3. Um duplo tem de guardar estado e não apenas responder. Se a plataforma decide sobre uma coluna, alguma coisa tem de mexer essa coluna.
  4. Não torne uma imitação mais estrita do que a coisa a sério. Um código de erro inventado testa um contrato que ninguém implementa.
  5. Transporte um parâmetro mesmo quando não o consegue honrar, para que um teste possa provar que ele chegou.
  6. Dois despertadores numa só base de dados tornam a medição aleatória. Um deles ganha e não se consegue dizer qual.
  7. Transforme a pré-condição do comentário numa verificação que corre primeiro. Passa então a ser um facto em vez de uma esperança.
  8. Prove uma guarda desligando-a. Um teto de repetições em que ninguém bateu é uma afirmação.
  9. Uma linha reivindicada é invisível para a consulta que procura trabalho vencido. Alguma coisa tem de repor as reivindicações obsoletas.
  10. Os nós não alcançados pertencem ao registo como saltados, ou uma saída antecipada fica indistinguível de outro percurso.

Para que fique claro o que isto é: sete ramos até aí por provar foram percorridos de ponta a ponta, cada um com uma contraprova vermelha; a verificação da fusão correu 16 de 16 passos sem uma linha de falha, a bateria de testes da web deu 4583 verdes, os pacotes, o gateway e as ferramentas em conjunto 7287 verdes, a bateria do próprio agente de imitação 481 verdes, o script de ponta a ponta 89 verdes, o ramo do despertador 13 verdes, o ramo do correio de aprovação 22 verdes e o ciclo com ações a sério 18 verdes. Por provar: nada foi executado contra um Proxmox a sério, um grafo ilegível na própria linha de espera, um lote maior do que o tamanho de lote, e a entrega de correio a sério por SMTP.

Do trabalho de plataforma na Provibr. Como a travessia aprendeu a parar e a continuar amanhã está em cada chamada recursiva estava em posição de cauda, e onde fica a linha entre uma pausa que um pedido consegue absorver e uma que suspende a execução está em uma pausa que mantinha a encomenda aberta.

Comece com um host e um agente

Crie uma conta, instale o agente com um único comando e ligue o seu primeiro host Proxmox. O período experimental dura 14 dias e não pede quaisquer dados de pagamento.