Blogue ·
Uma execução que imprimiu a própria falha
A verificação escrevia num resumo o código de saída de cada passo e escrevia DONE na mesma. Saiu uma versão por cima de uma linha vermelha que ninguém leu.
Por Provibr
Antes de uma versão corremos um único script. Ele executa a bateria de testes da web, as baterias dos pacotes, do gateway e das ferramentas, dez verificações de tipos, o lint, a verificação de conteúdo, o lint do protocolo e a geração de código do protocolo, e escreve uma linha por passo num ficheiro de resumo: o nome do passo e o seu código de saída. Depois escreve DONE.
Escrevia DONE mesmo quando alguns desses códigos de saída não eram zero.
A sete de setembro saiu uma versão enquanto o resumo continha uma linha que dizia bun test packages/gateway/tools exit 1. Eu tinha lido os contadores de aprovados e reprovados de duas baterias e não os da terceira. Nada se partiu: o teste que falhava era uma guarda sobre os cabeçalhos dos scripts de medição, portanto o produto ficou intacto e o cliente nunca viu nada. O desfecho foi bom e a forma estava errada, e a forma é a única parte que vale a pena registar.
Os contadores não são o portão
A razão pela qual isto é embaraçoso e não apenas azarado é que este modo de falha tem nome nos nossos próprios apontamentos, e o nome é verde vacuoso: uma verificação que não está ligada a uma decisão não é lida, e uma verificação lida a olho é lida como uma pessoa lê, ou seja, à procura daquilo que espera ver.
O resumo foi concebido para ser percorrido de relance. Cada bateria imprime os seus próprios totais algures acima, portanto o gesto natural é olhar para «4515 aprovados, 0 reprovados» e sentir-se despachado. Os códigos de saída estavam um ecrã mais abaixo, num ficheiro cuja última linha dizia DONE.
A reparação são quatro palavras de shell. Depois de todos os passos, o script procura no seu próprio resumo qualquer código de saída entre um e nove e, se encontrar um, acrescenta uma linha que começa por FAIL:. A regra que a acompanha é que o portão é um DONE sem um FAIL, e que os contadores não são o portão. A segunda metade dessa regra importa mais do que a primeira: quem tem um sinal correto e barato deixa de construir hábitos caros à volta de um sinal pouco fiável.
Há uma regra companheira: um comando de instalação nunca partilha uma cadeia de shell com uma verificação que apenas imprime. Se a verificação não consegue fazer falhar a cadeia, é documentação.
O que estava mesmo vermelho
A guarda que falhou faz parte de uma família e vale a pena descrevê-la, porque é invulgarmente barata para o que compra.
A pasta do gateway guarda os scripts de medição: varrimentos por navegador, sondas de disposição, ferramentas de sementeira e de limpeza, as coisas que uma ronda escreve para provar algo e depois deixa para trás. Hoje há pouco mais de quinhentos. São scripts simples, sem verificação de tipos, o que quer dizer que a única coisa entre eles e o apodrecimento é alguém, por acaso, executar um.
Quatro crivos passam por todos eles numa bateria de testes normal:
- Todos os scripts são analisáveis, usando o motor de execução que os corre de facto.
- Toda a importação relativa resolve para um ficheiro que existe. Essa é a forma de morte que um analisador não vê: o script é sintaticamente perfeito e cai na primeira linha quando o executa.
- Todos os scripts levam um cabeçalho com prosa dentro. Sem uma explicação, uma ferramenta de há quatro rondas é indistinguível de uma que ainda importa.
- Todos os cabeçalhos nomeiam a ronda de onde vieram, numa de exatamente duas formas fixas.
O quarto estava vermelho. Três scripts da ronda de documentação tinham cabeçalhos, e bons, mas nenhuma marca de ronda. É este o incidente inteiro: uma convenção de nomes, apanhada por uma guarda que existe para que quinhentos scripts continuem legíveis, sentada numa linha de resumo que ninguém leu.
Porque é que desconhecido tem de custar um hash de commit
O quarto crivo aceita duas formas e mais nenhuma. Ou o cabeçalho diz o nome da ronda com uma data e de onde veio essa atribuição, ou diz que a ronda é desconhecida e leva o hash do primeiro commit do ficheiro, com uma data.
O nome não pode explicitamente ser a palavra desconhecido. Sem essa exclusão, «ronda desconhecida, 2026-09-04» é um nome-com-data perfeitamente válido, e o ramo caro evita-se com uma palavra. Como está, admitir que não sabe custa-lhe um hash que alguém tem de ir procurar, e quando o encontrou já costuma saber qual foi a ronda.
A data é obrigatória por outra razão. Um nome nu é prosa e não uma marca: uma frase como «a ronda que também mexeu no trabalho dos cabeçalhos» contém um nome de ronda e não afirma nada sobre o ficheiro em que está. Com uma data agarrada torna-se uma afirmação sobre este script, feita num dia, que é a coisa mais pequena por que se pode responsabilizar alguém.
Um portão tem de estar a zero
A pergunta óbvia sobre quinhentos scripts sem tipos é porque não lhes fazemos verificação de tipos. Medimos isso, e a resposta é instrutiva.
A configuração existe. É um instrumento e não um portão, e não corre no script de verificação. Medido sobre os scripts tal como estavam:
| Configuração | Erros |
|---|---|
| Com as definições estritas deste espaço de trabalho | 3180 |
| Sem o modo estrito | 398 |
| Sem o modo estrito, depois de tornar tipável um ficheiro partilhado | 159 |
Um portão tem de assentar em zero, e esses 159 que ficam não são, na maioria, defeitos. Percorreram-se duas famílias e ambas se revelaram artefactos de tipar código sem tipos: um parâmetro sem valor por omissão que cai fora da forma inferida, e um ciclo com desestruturação sobre um vetor misto que alarga para uma união. Vinte e sete deles são um e o mesmo diagnóstico, sobre importar um ficheiro TypeScript a partir de um script que corre sob um carregador que lida com isso perfeitamente.
O que o instrumento comprou mesmo foi o passo de 398 para 159. Uma linha num auxiliar de navegador partilhado, um construtor que atribui os seus argumentos à instância em bloco, mantinha vivos 239 erros, porque todo o acesso a propriedades desse objeto em todos os scripts era irresolúvel. Uma reparação, 239 erros a menos. É um bom retorno para uma medição a que é permitido não ser zero, e é exatamente o tipo de retorno que não se obtém de um portão, porque um portão que não pode ser satisfeito acaba desligado.
A verificação que mediu o seu próprio motor de execução
Pôr a verificação de análise a funcionar levou duas tentativas, ambas falhadas de maneiras que pareciam problemas do produto.
A primeira versão pedia ao motor de execução em curso que verificasse cada ficheiro. Sob o nosso executor de testes, o motor em curso não é o motor sob o qual os scripts correm, e o seu sinalizador de verificação quer dizer uma coisa completamente diferente. O resultado foram 301 relatos de ficheiros sintaticamente partidos, com linhas de código citadas como texto de erro. Todos eles estavam bem.
A segunda versão nomeava explicitamente o motor correto e falhava na mesma, de outra maneira: o executor de testes colhe os processos filhos que pertencem a um teste, e a partir de mais ou menos meio da lista cada chamada voltava com um estado nulo. Morto por um sinal, não por um erro de análise. Uma guarda que fica vermelha ao acaso não é uma guarda.
A versão que funciona lança um processo filho que verifica ele próprio os ficheiros, oito de cada vez, em cerca de nove décimos de segundo quando isso foi medido. O que se afirma não mudou: se o motor de execução que corre estes scripts os aceita.
O paralelismo também não é um requinte. Corrida em série, a verificação ultrapassava o limite de tempo por teste, e uma guarda que esgota o tempo lê-se como uma guarda partida e não como uma guarda lenta.
O limite inferior que ninguém pede
Há um quinto teste nesse ficheiro que afirma apenas que há mais de cinquenta scripts para verificar.
Um percorredor de pastas que não encontra nada torna trivialmente verdadeira toda a afirmação que venha depois. Mude o nome da pasta, altere o filtro de extensões, parta o percorredor, e quatro crivos verdes relatam sobre uma lista vazia. O limite inferior está deliberadamente muito abaixo do número real, porque o objetivo não é impedir a limpeza; é tornar visível a diferença entre «não há nada de errado» e «nada foi examinado».
O mesmo ficheiro de testes é honesto sobre os seus limites, e essa honestidade faz parte do desenho. Nenhum dos quatro crivos prova que um script ainda mede o que diz medir. Um seletor que já não existe, uma rota que se mudou, uma coluna que foi removida: as quatro verificações passam. É um chão, não uma verificação de tipos, e dizê-lo no ficheiro impede a pessoa seguinte de tratar uma execução verde como mais do que ela é.
O que registaríamos
- Uma verificação que só imprime não é um portão. Tem de conseguir fazer falhar aquilo que vem a seguir.
- Escreva a linha de falha, não apenas os códigos de saída. O portão é uma palavra que alguém pode procurar com um grep, num sítio fixo, não um número que tenha de reparar.
- Nunca encadeie uma instalação atrás de uma verificação que não a pode travar. A cadeia é a promessa.
- Os contadores são um resumo, não um veredicto. As pessoas leem o número que esperam ver.
- Um portão tem de poder assentar em zero. Se uma medição não pode, guarde-a como instrumento e diga-o, ou será desativada e depois ignorada.
- Torne cara a fuga barata a uma regra. Se admitir ignorância é uma palavra, toda a gente escreve a palavra.
- Uma verificação que falha ao acaso não é uma verificação. Persiga a instabilidade antes de confiar no sinal.
- Afirme um limite inferior sobre o que o percorredor encontrou. Quatro verificações verdes sobre uma lista vazia parecem exatamente quatro verificações verdes.
Para que fique claro o que isto é: a linha de falha está no script que está no repositório, e desde então correu sobre integrações que estavam verdes. Ainda não foi provada por uma execução vermelha em uso real, apenas pelo resumo contra o qual foi escrita. Os números de tipagem vêm de uma medição num dia e a contagem de ficheiros mexe todas as noites.
Do trabalho de plataforma na Provibr. Outra guarda que teve de provar o seu próprio zero está em uma exceção de acessibilidade que se mede, e um campo que guardava em silêncio uma intenção em vez de um facto está em uma data futura não é um calendário.