Como funciona
O Sonda não reescreve nada que você veja: as cópias instrumentadas ficam num cache fora do projeto, os arquivos originais mantêm os números de linha, e o relatório sai do php-code-coverage.
- Um autoloader antes do do Composer. O bootstrap registra um autoloader antes do do Composer.
Uma classe do diretório alvo é carregada de uma cópia instrumentada, em cache por caminho, data de
modificação e versão do instrumentador. Os arquivos da chave
filesdo Composer que estão dentro do alvo (helpers) são reivindicados antes de o autoload do Composer rodar e carregados instrumentados logo depois. - Sondas por offset de byte. A instrumentação lê o arquivo com o
nikic/php-parsere insere as sondas por offset de byte no fonte original, sem reimprimir o código. Nenhuma linha muda de número, e__FILE__e__DIR__continuam apontando para o arquivo original. Uma sonda é uma atribuição num array (Hits::$current[<id do arquivo>][<id da sonda>] = 1); com o modo rápido, que é o padrão, uma referência local é ligada uma vez por função, e cada sonda vira uma única atribuição. - A janela de cada teste. Só as sondas executadas durante cada teste contam, a mesma janela que o PHPUnit dá ao Xdebug: código executado num data provider não conta como coberto.
- Arquivos carregados pelo caminho. O que o autoloader nunca vê vem instrumentado também: um
arquivo PHP do diretório alvo que o código carrega pelo caminho, com
includeourequire(uma migration do Laravel, um arquivo de rotas, um teste que fazrequiredo arquivo), abre a cópia instrumentada. Um stream wrapper defile://, registrado só nos processos que rodam testes, faz essa troca; toda outra operação de arquivo segue para o wrapper do próprio PHP. Ele só entra quando o diretório alvo tem arquivos que o autoloader não carrega (os que não declaram classe, interface, trait ou enum com nome, fora a chavefilesdo Composer).--includese--no-includesforçam a escolha. - O que cada teste executou. No fim de cada teste, o Sonda registra quais sondas ele executou,
com o arquivo, o tamanho (
#[Small],#[Medium],#[Large]), a duração e se falhou. Conjuntos iguais de sondas são guardados uma vez, porque os testes de uma suíte quase todos passam pelo mesmo código. É isso que o--affectede o--covered-byleem. - Uma execução por processo, um relatório. No fim de cada processo (cada worker do ParaTest
incluído), a execução é gravada. O comando junta as execuções de todos os workers e monta o
relatório com o php-code-coverage. Com
--covered-by, cada linha e branch cobertos levam os nomes dos testes que os executaram, como no Xdebug: no HTML, passar o mouse numa linha mostra esses testes, e um teste que falhou aparece em vermelho.
Custo
Uma sonda é uma atribuição num array, então o código instrumentado roda quase na velocidade do
original: na suíte Laravel da comparação, 174s com o Sonda contra 178s
sem cobertura, e 40s contra 42s com 10 workers do ParaTest. Pico de memória por worker, nos 10
workers de tests/Feature/Filament dessa suíte, com o wrapper de include ligado: 52 MB. Até a
1.14.2, esse wrapper prendia memória a cada operação de arquivo, e o pico chegava a 107 MB por worker.
Onde as peças ficam
Cache e execuções ficam fora do projeto, em $XDG_CACHE_HOME/sonda ou ~/.cache/sonda, num
diretório privado do usuário (modo 0700): as cópias instrumentadas rodam como código, então ninguém
mais pode escrever ali. O Sonda recusa um diretório de cache que pertença a outro usuário ou esteja
aberto a outros. O mapa de testes do --affected fica ali também, a menos que --map o mova.