Skip to main content

Testar consultas personalizadas

Você pode configurar testes para suas consultas do CodeQL a fim de garantir que eles continuem a retornar os resultados esperados nas novas versões da CodeQL CLI.

Quem pode usar esse recurso?

O CodeQL está disponível para os seguintes tipos de repositórios:

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 obter mais informações, 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-tests uma série de subdiretórios com testes para consultas armazenadas no diretório src. Cada subdiretório contém código de teste e um arquivo de referência QL que especifica a consulta a ser testada.
  • library-tests uma 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 bloco dependencies do pacote de teste. Para obter mais informações, 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 .ql no comando de teste, ele precisará ter o mesmo nome base que o arquivo correspondente .expected. Por exemplo, se a consulta for MyJavaQuery.ql, o arquivo de resultados esperado precisará ser MyJavaQuery.expected.

  • Se você quiser especificar um arquivo .qlref no 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 .qlref que faz referência a um arquivo .ql.
  • Caminho para um diretório que será pesquisado recursivamente para arquivos .ql e .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ção 0 corresponde 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

  1. Desenvolva a consulta. Por exemplo, a consulta simples a seguir localiza blocos vazios then no código Java:

    import java
    
    from IfStmt ifstmt
    where ifstmt.getThen() instanceof EmptyStmt
    select ifstmt, "This if statement has an empty then."
    
  2. Salve a consulta em um arquivo chamado EmptyThen.ql em um diretório com outras consultas personalizadas. Por exemplo, custom-queries/java/queries/EmptyThen.ql.

  3. 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 arquivo qlpack.yml com o seguinte conteúdo a custom-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".

  4. Crie um pacote do CodeQL para testes Java adicionando um arquivo qlpack.yml com o seguinte conteúdo a custom-queries/java/tests, atualizando o dependencies para corresponder ao nome do pacote do CodeQL de consultas personalizadas:

    O arquivo qlpack.yml a seguir informa que my-github-user/my-query-tests depende de my-github-user/my-custom-queries em 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 Java extractor ao criar bancos de dados de teste. A linha tests: . declara que todos os arquivos .ql no pacote devem ser executados como testes quando codeql test run é executado com a opção --strict-test-discovery. Normalmente, os pacotes de teste não contêm uma propriedade version. 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: .
    
  5. Execute codeql pack install na raiz do diretório de teste. Isso gera um arquivo codeql-pack.lock.yml que especifica todas as dependências transitivas necessárias para executar consultas neste pacote.

  6. 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.

  7. No novo diretório, crie EmptyThen.qlref para definir o local de EmptyThen.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 chamado my-custom-queries, que é declarado como uma dependência para my-query-tests. Portanto, EmptyThen.qlref deve simplesmente conter EmptyThen.ql.

  8. Crie um snippet de código para ser testado. O código Java a seguir contém uma instrução vazia if na terceira linha. Salve-o em custom-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:

  1. Localizará um teste no diretório EmptyThen.

  2. Extrairá um banco de dados do CodeQL dos arquivos .java armazenados no diretório EmptyThen.

  3. 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/EmptyThen

    Para 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".

  4. Executará o teste executando a consulta e gerando um arquivo de resultados EmptyThen.actual.

  5. Verificará se há um arquivo EmptyThen.expected a ser comparado com o arquivo de resultados .actual.

  6. 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 executando test run com 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 obter mais informações, confira "Como gerenciar bancos de dados do CodeQL".

Leitura adicional