71 arquivos de teste, 845 testes, e nenhum deles estava rodando
Durante algumas semanas o pnpm test do meu projeto terminava assim:
❯ test/analyzeChange.test.ts (0 test)
❯ test/rowMapper.test.ts (0 test)
... 69 arquivos ...
Test Files no tests
Tests no tests
Todos os 71 arquivos imprimiam No test suite found in file, e a execução saía com código diferente de
zero.
Eu li isso como problema de configuração. E é exatamente com o que parece: um glob de include errado,
uma entrada de projects apontando para o diretório errado, um environment incompatível — aquele tipo
de coisa em que o runner está bem e o seu setup está torto. Então ficou lá.
O estado real era que 845 testes não executavam nem uma vez, e nada na tela dizia isso com essas
palavras.
A causa era uma linha que eu mesmo escrevi
No pnpm-workspace.yaml:
overrides:
# Patch de segurança: advisories de vite que só afetam dev.
vite: ">=6.4.3"
Eu não dependo do vite diretamente. Ele chega de forma transitiva, embaixo do vitest. Saiu um conjunto de
advisories, a versão corrigida era a 6.4.3, e um override foi o caminho mais curto para garantir que toda
cópia na árvore estivesse igual ou acima disso.
Só que >=6.4.3 não é um pin. É um intervalo aberto — e um override tem precedência sobre o que a
dependência declara. Quando o vite 8.1.4 saiu, o pnpm resolveu para ele, mesmo o vitest 3.2.7
declarando ^5 || ^6 || ^7.0.0-0, que não inclui a 8.
Essa é a parte que vale guardar: o intervalo que o vitest publica era exatamente a proteção que teria
impedido isso, e overrides é o mecanismo que manda o resolver ignorar essa proteção. Eu desliguei a
verificação e deixei um comentário explicando o motivo — e o comentário não dizia nada sobre o teto,
porque na época não existia nada acima.
Por que o vite 8 devolve zero suítes em vez de um erro
O vite 8 migrou o pipeline de transformação para rolldown/oxc, e o contrato do module-runner mudou junto.
O vitest carregou os 71 arquivos sem lançar exceção, avaliou todos, e não recebeu nenhuma suíte
registrada de volta.
Do ponto de vista do runner isso é indistinguível de um arquivo sem nenhum describe/test dentro.
Então ele reportou a coisa honesta que conseguia ver, uma vez por arquivo: No test suite found in file.
Existia uma única pista da causa real no meio do ruído, e eu vinha passando o olho por ela sem parar:
The `esbuild` option is deprecated, please use `oxc` instead
Esse aviso é o vite 8 se anunciando. Nada na saída jamais mencionou incompatibilidade de versão.
A correção
vite: ">=6.4.3 <8"
Antes de colocar um teto, vale checar quem realmente pede o pacote:
$ pnpm why vite
vitest
vite-node
@vitest/mocker
Só o runner de testes. Nada nos dois workspaces consome vite em build nem envia para produção, então
restringir não tem raio de impacto nenhum. Se a resposta tivesse incluído o framework ou o bundler, o
teto seria uma decisão de verdade em vez de ser de graça.
O vite caiu para 7.3.6. Os 71 arquivos foram coletados. 845 testes, todos passando.
E então um deles falhou
Não era regressão do período em que a suíte estava morta — nada que eu tinha mexido nesse intervalo
quebrou. A falha era mais antiga que isso, e era real.
Uma função minha montava colunas do banco a partir dos fatos extraídos de um evento de depreciação. Ela
tratava a data de desligamento e o modelo sucessor. Não havia ramo nenhum para a data de depreciação.
Ou seja: a coluna announced_at estava NULL. Não em algumas linhas — em todas as linhas já
escritas. E é dela que sai o início do período de aviso, isto é, quanto tempo o fornecedor deu antes de
desligar um modelo. Para um projeto cujo assunto inteiro é ciclo de vida de modelo, não é uma falha
cosmética.
O teste que pega isso já existia, e existia esse tempo todo. O nome dele é
shutdown date, successor and announcement date. A extração estava lá. A assertiva estava lá. Faltava só
o mapeamento para a coluna — e o único processo que teria dito isso estava devolvendo no tests.
Depois disso rodei o verificador de tipos, que eu também não rodava há um tempo: exit 2. Um arquivo
lia um campo que a função nunca retornava. As duas verificações automáticas do repositório estavam
falhando, de duas formas diferentes, e nenhuma delas era alta o suficiente para interromper alguém.
O que eu levo disso
Um override de segurança precisa de teto também. O motivo de você escrever um é saber mais que o
resolver sobre uma borda inferior específica. Esse conhecimento não se estende para cima. Um >=x
publicado hoje é uma aposta em toda versão maior que sair depois que você parar de prestar atenção.
no tests é um estado de falha, não um estado neutro. Uma execução que coleta zero suítes carrega a
mesma informação que uma execução que falha: você não sabe se o seu código funciona. Ela já sai com
código diferente de zero — a lacuna estava na minha leitura, não na ferramenta.
Isso saiu de um projeto que faz crawl da documentação de 15 fornecedores de IA e registra cada mudança
com a data em que aconteceu: aichangewatch.com