Skip to main content

Command Palette

Search for a command to run...

DirectoryNotFoundException: quando o teste do dev conhece só a máquina dele

Updated
8 min read
DirectoryNotFoundException: quando o teste do dev conhece só a máquina dele
V
Documentando o que faço, resolvo e quebro. Foco total em problemas reais e no passo a passo da investigação. Escrevo para organizar como minha mente pensa diante dos problemas e deixo aberto para quem quiser acompanhar.

Me avisaram que a pipeline de um projeto tinha quebrado. Abri e fui direto na task que havia falhado e a mensagem era clara:

Antes de qualquer coisa, avisei no grupo do time que a pipeline estava com problema e que eu estava verificando.


O problema

Bom, a mensagem de erro dizia que o agente não encontrou o diretório de templates de email. Bem tranquilo de entender, mas o contexto ajudou muito. No dia anterior, o dev tinha mandado no grupo:

Galera, esse projeto aqui fica os templates de email. Se alterar o arquivo por outro projeto, os commits serão bloqueados. Quando precisar alterar, altere no repositório real e depois atualize nos outros projetos.

Com isso, eu já tinha uma hipótese antes de abrir os arquivos: algum teste novo passou a depender desses templates na hora de rodar.


A investigação

Entendendo o template de pipeline

A task de testes vinha de um template centralizado que eu mesmo montei:

Fui até o repositório central, para relembrar o que o template faz. Ele descobre automaticamente os projetos de teste procurando por arquivos .csproj, e para cada um monta e executa o dotnet test. Se qualquer projeto crítico falha, a pipeline inteira cai com exit 1.

Nada suspeito aqui. Mas foi importante para ter contexto. Quanto mais contexto eu absorvo, mais rápido consigo fechar o diagnóstico.

O que mudou na PR

Fui olhar a PR que havia sido mergeada antes da falha. Tinha vários testes novos. Peguei qualquer um e encontrei:

Fui até onde TemplatesRootPath era definido:

Olha que interessante, cinco .. navegando para fora do diretório. O dev provavelmente construiu o caminho com base na estrutura de pastas da máquina dele.

Na máquina dele, subindo cinco níveis a partir do BaseDirectory, chegava no repositório oficial dos templates. No agente de CI, esse caminho não existe. Daí o DirectoryNotFoundException. Isso bateu com a mensagem do dia anterior no grupo. O código não estava tentando chegar na pasta réplica usada no agente. Estava tentando alcançar o repositório oficial dos templates, do jeito que existe na estrutura local do dev.

Não era problema de pipeline. Era problema de código.

A solução: variável de ambiente com fallback

O dev é tech lead e tem uma pipeline parada. Não era hora de questionar a arquitetura da solução local dele. A saída mais rápida e menos invasiva: fazer o código consultar uma variável de ambiente antes de tentar o caminho relativo, e configurar essa variável no grupo de variáveis compartilhado já existente, apontando para a pasta réplica correta no agente.

Primeiro, o ajuste no TemplatesRootPath:

Depois, no grupo de variáveis, adicionei EMAIL_TEMPLATES_PATH apontando para AppData/SharedFiles. E no template da pipeline, passei a variável para o step de testes:

Rodei. Os erros de DirectoryNotFoundException sumiram. Mas a pipeline ainda falhou, agora com 6 testes quebrando onde antes eram 25.

Lendo os logs do xUnit sem enlouquecer

Uma dica que vale guardar: o xUnit roda testes em paralelo, então o output fica entrelaçado. O padrão é mais ou menos assim:

É muito fácil se confundir. A dica é dar um Ctrl+F e procurar por Failed. Ignore os blocos [xUnit.net], principalmente os que aparecem no meio das explicações: eles são só avisos que interrompem o output de outro teste. Não sei por que é assim xD, é muito ruim. Mas sabendo disso, fica gerenciável.

E sobre o stack trace: leia de baixo para cima. Ignore tudo que começa com System. ou Microsoft., isso é infraestrutura do .NET que você não controla. Foca na primeira linha que aponta para um arquivo do seu projeto.

Quando o valor não nasce no arquivo que eu estou olhando, eu já sei que ele veio de fora. E aí o stack trace vira mapa: eu subo uma chamada por vez até encontrar a origem.

Os 6 erros restantes: todos a mesma causa

Erros 1, 2 e 3 (AppDataUnitTest.cs)

Os três apontavam para variações do mesmo padrão: um método GetSharedFilesPath dentro dos testes usando os mesmos cinco .. para montar o caminho. O dev copiou a lógica do TemplatesRootPath que já havíamos corrigido. A correção foi idêntica: ler de EMAIL_TEMPLATES_PATH com fallback para o caminho relativo.

Erros 4, 5 e 6 (RenderPDFUnitTest.cs --> invoice.cshtml, invoice-email.cshtml, invoice-usd.cshtml)

Stack trace idêntico para os três, só mudava o arquivo de template. A rota era:

O teste passava um caminho montado com GetSharedFilesPath usando os mesmos cinco ... O GetRazorEngineCompiledTemplate tentava o caminho direto, não achava, caía no GetFilePath, que também não achava, e estourava. Mesma raiz, mesma correção: aplicar o padrão de variável de ambiente com fallback no GetSharedFilesPath do RenderPDFUnitTest.

No total: três arquivos de teste, mesma lógica copiada, mesma correção aplicada em cada um.

Subi as correções, rodei a pipeline. Build finalizado com sucesso. Abri a PR, avisei o dev, ela foi mergeada. Rodei a branch develop para validar uma última vez.

O real problema estava no código do teste. A pipeline foi só o mensageiro.


Ficou em aberto

A lógica dos cinco .. está correta na máquina do dev? Não investiguei se a estrutura de diretórios local realmente funciona como o código assume. Pode ser que funcione, pode ser que o dev nunca tenha rodado esses testes localmente de verdade.

Existe uma abordagem melhor para referenciar arquivos compartilhados em testes? A variável de ambiente com fallback resolve o problema de pipeline, mas o padrão de navegar para fora do projeto para achar um arquivo irmão em repositório diferente tem cheiro de problema esperando para acontecer de novo. Uma solução mais robusta provavelmente envolveria copiar os templates como parte do build de teste, ou usar um fixture de teste que isola esse caminho. Não avaliei isso a fundo.

Quantos outros lugares no projeto usam a mesma lógica? Corrigi três arquivos. Pode haver mais. Não fiz uma busca global por "..", "..", "..", "..", ".." no repositório.


Referências


Vinicius Aguilar — DevOps Engineer

War Room

Part 2 of 3

Incidentes reais, investigações reais. Cada post aqui é um problema que apareceu em produção com o erro, o raciocínio da investigação e a causa raiz. Sem filtro, sem romantizar. É assim que as coisas funcionam na prática.

Up next

Como editar o título de uma PR derrubou todos os builds do dia

Pipeline parada. Dev sem conseguir buildar. E um erro apontando para um diretório que não deveria existir. O que parecia ser um problema na VM era o rastro de três ações em uma PR que ninguém tinha pe