Sobre o teste de consultas personalizadas
O CodeQL fornece uma estrutura de teste simples para teste de regressão automatizado de consultas. Teste as consultas para garantir que elas se comportem conforme o esperado.
Durante um teste de consulta, o CodeQL compara os resultados que o usuário espera que a consulta produza com aqueles que realmente foram produzidos. Se os resultados esperados e reais forem diferentes, o teste de consulta falhará. Para corrigir o teste, você deve iterar na consulta e nos resultados esperados até que os resultados reais e os resultados esperados correspondam exatamente. Este tópico mostra como criar arquivos de teste e executar testes neles usando o subcomando test run.
Como configurar um pacote do CodeQL de teste para consultas personalizadas
Todos os testes do CodeQL precisam ser armazenados em um pacote especial de "teste" do CodeQL. Ou seja, um diretório de arquivos de teste com um arquivo qlpack.yml que defina:
name: <name-of-test-pack>
version: 0.0.0
dependencies:
<codeql-libraries-and-queries-to-test>: "*"
extractor: <language-of-code-to-test>
O valor dependencies especifica os pacotes do CodeQL que contêm as consultas a serem testadas.
Normalmente, esses pacotes serão resolvidos na origem e, portanto, não é necessário especificar uma versão fixa do pacote. O extractor define qual linguagem a CLI usará para criar bancos de dados de teste por meio dos arquivos de código armazenados nesse pacote do CodeQL. Para saber mais, confira Como personalizar a análise com pacotes CodeQL.
Você pode achar útil examinar a forma como os testes de consulta são organizados no repositório do CodeQL. Cada linguagem tem um diretório src, o ql/<language>/ql/src, que contém bibliotecas e consultas para analisar bases de código. Junto com o diretório src, há um diretório test com testes para essas bibliotecas e consultas.
Cada diretório test é configurado como um pacote do CodeQL de teste com dois subdiretórios:
query-testsuma série de subdiretórios com testes para consultas armazenadas no diretóriosrc. Cada subdiretório contém código de teste e um arquivo de referência QL que especifica a consulta a ser testada.library-testsuma série de subdiretórios com testes para arquivos de biblioteca QL. Cada subdiretório contém códigos de teste e consultas que foram gravadas como testes de unidade para uma biblioteca.
Depois de criar o arquivo qlpack.yml, é necessário baixar todas as dependências e disponibilizá-las para a CLI. Faça isso executando o seguinte comando no mesmo diretório do arquivo qlpack.yml:
codeql pack install
Isso gera um arquivo codeql-pack.lock.yml que especifica todas as dependências transitivas necessárias para executar consultas neste pacote. É preciso fazer check-in desse arquivo no controle do código-fonte.
Como configura os arquivos de teste para uma consulta
Para cada consulta que deseja testar, você deve criar um subdiretório no pacote do CodeQL. Depois, adicione os seguintes arquivos ao subdiretório antes de executar o comando de teste:
-
Um arquivo de referência de consulta (arquivo
.qlref) que define o local da consulta a ser testada. O local é definido em relação à raiz do pacote do CodeQL que contém a consulta. Normalmente, esse é um pacote do CodeQL especificado no blocodependenciesdo pacote de teste. Para saber mais, confira Arquivos de referência de consulta.Você não precisará adicionar um arquivo de referência de consulta se a consulta que deseja testar estiver armazenada no diretório de teste, mas uma boa prática é armazenar as consultas separadamente dos testes. A única exceção são os testes de unidade para bibliotecas QL, que costumam ser armazenados em pacotes de teste, separados das consultas que geram alertas ou caminhos.
-
O código de exemplo no qual você deseja executar a consulta. Isso deve consistir em um ou mais arquivos contendo exemplos de código que a consulta foi projetada para identificar.
Você também pode definir os resultados esperados ao executar a consulta no código de exemplo, criando um arquivo com a extensão .expected. Como alternativa, você pode deixar o comando de teste para criar o arquivo .expected.
Para obter um exemplo mostrando como criar e testar uma consulta, veja o exemplo abaixo.
Observação
Os arquivos .ql, .qlref e .expected precisam ter nomes consistentes:
- Se você quiser especificar diretamente o próprio arquivo
.qlno comando de teste, ele precisará ter o mesmo nome base que o arquivo correspondente.expected. Por exemplo, se a consulta forMyJavaQuery.ql, o arquivo de resultados esperado precisará serMyJavaQuery.expected. - Se você quiser especificar um arquivo
.qlrefno comando, ele deverá ter o mesmo nome base que o arquivo correspondente.expected, mas a consulta em si poderá ter um nome diferente. - Os nomes dos arquivos de código de exemplo não precisam ser consistentes com os outros arquivos de teste. Todos os arquivos de código de exemplo encontrados ao lado do arquivo
.qlref(ou.ql) e nos subdiretórios serão usados para criar um banco de dados de teste. Portanto, para simplificar, recomendamos que você não salve os arquivos de teste em diretórios que sejam ancestrais uns dos outros.
Em execuçãocodeql test run
Os testes de consulta do CodeQL são realizados executando o seguinte comando:
codeql test run <test|dir>
O argumento <test|dir> pode ser um ou mais destes:
- Caminho para um arquivo
.ql. - Caminho para um arquivo
.qlrefque faz referência a um arquivo.ql. - Caminho para um diretório que será pesquisado recursivamente para arquivos
.qle.qlref.
Você também pode especificar:
--threads:opcionalmente, o número de threads a serem usados ao executar consultas. A opção padrão é1. Você pode especificar mais threads para acelerar a execução da consulta. A especificação0corresponde o número de threads ao número de processadores lógicos.
Para obter detalhes completos de todas as opções que você pode usar ao testar consultas, confira test run.
Exemplo
O exemplo a seguir mostra como configurar um teste para uma consulta que pesquisa código Java em busca de instruções if que tenham blocos vazios then. Ele inclui etapas para adicionar a consulta personalizada e os arquivos de teste correspondentes a fim de separar os pacotes do CodeQL fora do check-out do repositório do CodeQL. Isso garante que, ao atualizar as bibliotecas do CodeQL ou fazer check-out de um branch diferente, você não substitua as consultas e os testes personalizados.
Preparar uma consulta e arquivos de teste
-
Desenvolva a consulta. Por exemplo, a consulta simples a seguir localiza blocos vazios
thenno código Java:import java from IfStmt ifstmt where ifstmt.getThen() instanceof EmptyStmt select ifstmt, "This if statement has an empty then." -
Salve a consulta em um arquivo chamado
EmptyThen.qlem um diretório com outras consultas personalizadas. Por exemplo,custom-queries/java/queries/EmptyThen.ql. -
Se você ainda não adicionou as consultas personalizadas a um pacote do CodeQL, crie um pacote do CodeQL agora. Por exemplo, se as consultas Java personalizadas forem armazenadas no
custom-queries/java/queries, adicione um arquivoqlpack.ymlcom o seguinte conteúdo acustom-queries/java/queries:name: my-custom-queries dependencies: codeql/java-queries: "*"Para obter mais informações sobre os pacotes do CodeQL, confira Como personalizar a análise com pacotes CodeQL.
-
Crie um pacote do CodeQL para testes Java adicionando um arquivo
qlpack.ymlcom o seguinte conteúdo acustom-queries/java/tests, atualizando odependenciespara corresponder ao nome do pacote do CodeQL de consultas personalizadas:O arquivo
qlpack.ymla seguir informa quemy-github-user/my-query-testsdepende demy-github-user/my-custom-queriesem uma versão igual ou superior a 1.2.3 e inferior a 2.0.0. Ele também declara que a CLI deve usar o Javaextractorao criar bancos de dados de teste. A linhatests: .declara que todos os arquivos.qlno pacote devem ser executados como testes quandocodeql test runé executado com a opção--strict-test-discovery. Normalmente, os pacotes de teste não contêm uma propriedadeversion. Isso impede que você os publique acidentalmente.name: my-github-user/my-query-tests dependencies: my-github-user/my-custom-queries: ^1.2.3 extractor: java-kotlin tests: . -
Execute
codeql pack installna raiz do diretório de teste. Isso gera um arquivocodeql-pack.lock.ymlque especifica todas as dependências transitivas necessárias para executar consultas neste pacote. -
No pacote de teste Java, crie um diretório para conter os arquivos de teste associados a
EmptyThen.ql. Por exemplo,custom-queries/java/tests/EmptyThen. -
No novo diretório, crie
EmptyThen.qlrefpara definir o local deEmptyThen.ql. O caminho para a consulta precisa ser especificado em relação à raiz do pacote do CodeQL que contém a consulta. Nesse caso, a consulta está no diretório de nível superior do pacote do CodeQL chamadomy-custom-queries, que é declarado como uma dependência paramy-query-tests. Portanto,EmptyThen.qlrefdeve simplesmente conterEmptyThen.ql. -
Crie um snippet de código para ser testado. O código Java a seguir contém uma instrução vazia
ifna terceira linha. Salve-o emcustom-queries/java/tests/EmptyThen/Test.java.class Test { public void problem(String arg) { if (arg.isEmpty()) ; { System.out.println("Empty argument"); } } public void good(String arg) { if (arg.isEmpty()) { System.out.println("Empty argument"); } } }
Executar o teste
Para executar o teste, acesse o diretório custom-queries e execute codeql test run java/tests/EmptyThen.
Ao ser executado, o teste:
-
Localizará um teste no diretório
EmptyThen. -
Extrairá um banco de dados do CodeQL dos arquivos
.javaarmazenados no diretórioEmptyThen. -
Compilará a consulta referenciada pelo arquivo
EmptyThen.qlref.Se essa etapa falhar, será porque a CLI não consegue encontrar o pacote personalizado do CodeQL. Execute novamente o comando e especifique o local do pacote personalizado do CodeQL, por exemplo:
codeql test run --search-path=java java/tests/EmptyThenPara obter mais informações sobre como salvar o caminho de pesquisa como parte da configuração, confira Como especificar opções de comando em um arquivo de configuração do CodeQL.
-
Executará o teste executando a consulta e gerando um arquivo de resultados
EmptyThen.actual. -
Verificará se há um arquivo
EmptyThen.expecteda ser comparado com o arquivo de resultados.actual. -
Relatará os resultados do teste. Neste caso, uma falha:
0 tests passed; 1 tests failed:. O teste falhou porque ainda não adicionamos um arquivo com os resultados esperados da consulta.
Exibir a saída do teste de consulta
O CodeQL gera os seguintes arquivos no diretório EmptyThen:
EmptyThen.actual, um arquivo que contém os resultados reais gerados pela consulta.EmptyThen.testproj, um banco de dados de teste que você pode carregar no VS Code e usar para depurar testes com falha. Quando os testes são concluídos com êxito, esse banco de dados é excluído em uma etapa de manutenção. Você pode substituir essa etapa executandotest runcom a opção--keep-databases.
Nesse caso, a falha era esperada e fácil de ser corrigida. Se você abrir o arquivo EmptyThen.actual, verá os resultados do teste:
| Test.java:3:5:3:22 | stmt | This if statement has an empty then. |
Esse arquivo contém uma tabela, com uma coluna de local do resultado, juntamente com colunas separadas para cada parte da cláusula select que a consulta gera.
Como os resultados são o esperado, podemos atualizar a extensão de arquivo para definir isso como o resultado esperado desse teste (EmptyThen.expected).
Se você executar novamente o teste agora, a saída será semelhante, mas será concluída relatando: All 1 tests passed..
Se os resultados da consulta forem alterados, por exemplo, se você revisar a instrução select da consulta, o teste falhará. Para resultados com falha, a saída da CLI inclui uma comparação unificada dos arquivos EmptyThen.expected e EmptyThen.actual.
Essas informações podem ser suficientes para depurar falhas de teste triviais.
Para falhas mais difíceis de serem depuradas, você pode importar EmptyThen.testproj no CodeQL para VS Code, executar EmptyThen.ql e exibir os resultados no código de exemplo Test.java. Para saber mais, confira Como gerenciar bancos de dados do CodeQL.