Skip to main content

Pruebas de consultas personalizadas

Puedes configurar pruebas para las consultas de CodeQL a fin de asegurarte de que siguen devolviendo los resultados esperados con versiones nuevas de la CodeQL CLI.

¿Quién puede utilizar esta característica?

CodeQL está disponible para los siguientes tipos de repositorios:

Acerca de las pruebas de consultas personalizadas

CodeQL proporciona un marco de pruebas sencillo para pruebas de regresión automatizadas de consultas. Prueba las consultas para asegurarte de que se comportan según lo previsto.

Durante una prueba de consulta, CodeQL compara los resultados que el usuario prevé que la consulta genere con los producidos realmente. Si los resultados esperados y los reales difieren, se produce un error en la prueba de la consulta. Para corregir la prueba, debe recorrer en iteración la consulta y los resultados esperados hasta que los resultados reales y los esperados coincidan totalmente. En este tema se muestra cómo crear archivos de prueba y ejecutar pruebas en ellos mediante el subcomando test run.

Configuración de un paquete de pruebas de CodeQL para consultas personalizadas

Todas las pruebas de CodeQL deben almacenarse en un paquete especial de "prueba" de CodeQL. Es decir, un directorio para los archivos de prueba con un archivo qlpack.yml que define lo siguiente:

name: <name-of-test-pack>
version: 0.0.0
dependencies:
  <codeql-libraries-and-queries-to-test>: "*"
extractor: <language-of-code-to-test>

El valor dependencies especifica los paquetes de CodeQL que contienen consultas que se van a probar. Normalmente, estos paquetes se resolverán desde el origen, por lo que no es necesario especificar una versión fija del paquete. extractor define qué lenguaje usará la CLI para crear bases de datos de prueba a partir de los archivos de código almacenados en este paquete de CodeQL. Para obtener más información, vea «Personalización del análisis con paquetes de CodeQL».

Es posible que le resulte útil examinar la forma en que se organizan las pruebas de consulta en el repositorio de CodeQL. Cada lenguaje tiene un directorio src, ql/<language>/ql/src, que contiene bibliotecas y consultas para analizar los códigos base. Junto con el directorio src, hay otro test con pruebas para estas bibliotecas y consultas.

Cada directorio test se configura como un paquete de prueba de CodeQL con dos subdirectorios:

  • query-tests es una serie de subdirectorios con pruebas para consultas almacenadas en el directorio src. Cada subdirectorio contiene código de prueba y un archivo de referencia QL que especifica la consulta que se va a probar.
  • library-tests es una serie de subdirectorios con pruebas para archivos de biblioteca QL. Cada subdirectorio contiene código de prueba y consultas que se escribieron como pruebas unitarias para una biblioteca.

Después de crear el archivo qlpack.yml, debes asegurarte de que todas las dependencias se descarguen y estén disponibles para la CLI. Para ello, ejecute el siguiente comando en el mismo directorio que el archivo qlpack.yml:

codeql pack install

Esto generará un archivo codeql-pack.lock.yml que especifica todas las dependencias transitivas necesarias para ejecutar consultas en este paquete. Este archivo debe estar registrado en el control de código fuente.

Configuración de los archivos de prueba para una consulta

Para cada consulta que quieras probar, debes crear un subdirectorio en el paquete de prueba de CodeQL. Después, agrega los archivos siguientes al subdirectorio antes de ejecutar el comando de prueba:

  • Un archivo de referencia de consulta (archivo .qlref) que define la ubicación de la consulta que se va a probar. La ubicación se define en relación con la raíz del paquete de CodeQL que contiene la consulta. Normalmente, se trata de un paquete de CodeQL especificado en el bloque dependencies del paquete de pruebas. Para obtener más información, vea «Archivos de referencia de consulta».

    No es necesario agregar un archivo de referencia de consulta si la consulta que quieres probar se almacena en el directorio de prueba, pero por lo general es recomendable almacenar consultas independientemente de las pruebas. La única excepción son las pruebas unitarias para las bibliotecas de QL, que tienden a almacenarse en paquetes de prueba, independientes de las consultas que generan alertas o rutas de acceso.

  • El código de ejemplo en el que quieres ejecutar la consulta. Esto debe estar compuesto de uno o varios archivos que contengan ejemplos del código que la consulta esté diseñada para identificarse.

También puedes definir los resultados que esperas ver al ejecutar la consulta en el código de ejemplo; para ello, crea un archivo con la extensión .expected. Como alternativa, puedes dejar el comando de prueba para crear el archivo .expected automáticamente.

En el ejemplo siguiente se muestra cómo crear y probar una consulta.

Note

Los archivos .ql, .qlref y .expected deben tener nombres coherentes:

  • Si quieres especificar directamente el propio archivo .ql en el comando de prueba, debe tener el mismo nombre base que el archivo .expected correspondiente. Por ejemplo, si la consulta es MyJavaQuery.ql, el archivo de resultados esperado debe ser MyJavaQuery.expected.
  • Si quieres especificar un archivo .qlref en el comando, debe tener el mismo nombre base que el archivo .expected correspondiente, pero la propia consulta puede tener un nombre diferente.
  • Los nombres de los archivos de código de ejemplo no tienen que ser coherentes con los demás archivos de prueba. Todos los archivos de código de ejemplo encontrados junto al archivo .qlref (o .ql) y en cualquier subdirectorio se usarán para crear una base de datos de prueba. Por lo tanto, por motivos de simplicidad, se recomienda no guardar archivos de prueba en directorios que sean predecesores entre sí.

En ejecución codeql test run

Para ejecutar las pruebas de consulta de CodeQL, ejecute el comando siguiente:

codeql test run <test|dir>

El argumento <test|dir> puede ser uno de los siguientes:

  • Ruta de acceso a un archivo .ql.
  • Ruta de acceso a un archivo .qlref que haga referencia a un archivo .ql.
  • Ruta de acceso a un directorio en el que se buscarán de forma recursiva los archivos .ql y .qlref.

También puede especificar:

  • --threads: de forma opcional: cantidad de subprocesos que se van a usar cuando se ejecutan las consultas. La opción predeterminada es 1. Puede especificar más subprocesos para acelerar la ejecución de consultas. La especificación de 0 hace coincidir el número de subprocesos con el número de procesadores lógicos.

Para obtener detalles completos de todas las opciones que puedes usar al probar consultas, consulta test run.

Ejemplo

En el ejemplo siguiente se muestra cómo configurar una prueba para una consulta que busca código Java para instrucciones if que tienen bloques then vacíos. Incluye pasos para agregar la consulta personalizada y los archivos de prueba correspondientes a fin de separar los paquetes de CodeQL fuera de la restauración del repositorio de CodeQL. Esto garantiza que, al actualizar las bibliotecas de CodeQL o restaurar una rama diferente, no sobrescribirás las consultas y pruebas personalizadas.

Preparación de una consulta y archivos de prueba

  1. Desarrolla la consulta. Por ejemplo, la siguiente consulta sencilla busca bloques then vacíos en el código de Java:

    import java
    
    from IfStmt ifstmt
    where ifstmt.getThen() instanceof EmptyStmt
    select ifstmt, "This if statement has an empty then."
    
  2. Guarda la consulta en un archivo denominado EmptyThen.ql en un directorio con las demás consultas personalizadas. Por ejemplo, custom-queries/java/queries/EmptyThen.ql.

  3. Si aún no has agregado las consultas personalizadas a un paquete de CodeQL, crea ahora un paquete de CodeQL. Por ejemplo, si las consultas de Java personalizadas se almacenan en custom-queries/java/queries, agrega un archivo qlpack.yml con el contenido siguiente a custom-queries/java/queries:

    name: my-custom-queries
    dependencies:
      codeql/java-queries: "*"
    

    Para obtener más información sobre los paquetes de CodeQL, consulta "Personalización del análisis con paquetes de CodeQL".

  4. Crea un paquete de CodeQL para las pruebas de Java agregando un archivo qlpack.yml con el siguiente contenido a custom-queries/java/tests. De este modo, se actualiza dependencies para que coincida con el nombre del paquete de CodeQL de consultas personalizadas:

    El siguiente archivo qlpack.yml indica que my-github-user/my-query-tests depende de my-github-user/my-custom-queries una versión mayor o igual que 1.2.3 y menor que 2.0.0. También declara que la CLI debe usar el extractor (extractor) de Java al crear bases de datos de prueba. La línea tests: . declara que todos los archivos .ql del paquete se deben ejecutar como pruebas cuando codeql test run se ejecuta con la opción --strict-test-discovery. Normalmente, los paquetes de prueba no contienen una propiedad version. Esto evita que se publiquen accidentalmente.

    name: my-github-user/my-query-tests
    dependencies:
      my-github-user/my-custom-queries: ^1.2.3
    extractor: java-kotlin
    tests: .
    
  5. Ejecuta codeql pack install en la raíz del directorio de prueba. Esto genera un archivo codeql-pack.lock.yml que especifica todas las dependencias transitivas necesarias para ejecutar consultas en este paquete.

  6. En el paquete de pruebas de Java, crea un directorio para que contenga los archivos de prueba asociados a EmptyThen.ql. Por ejemplo, custom-queries/java/tests/EmptyThen.

  7. En el directorio nuevo, crea EmptyThen.qlref para definir la ubicación de EmptyThen.ql. La ruta de acceso a la consulta debe especificarse en relación con la raíz del paquete de CodeQL que contiene la consulta. En este caso, la consulta se encuentra en el directorio de nivel superior del paquete de CodeQL denominado my-custom-queries, que se declara como una dependencia de my-query-tests. Por lo tanto, EmptyThen.qlref simplemente debe contener EmptyThen.ql.

  8. Crea un fragmento de código para probarlo. El código de Java siguiente contiene una instrucción if vacía en la tercera línea. Guárdalo en 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");
        }
      }
    }
    

Ejecución de la prueba

Para ejecutar la prueba, muévela al directorio custom-queries y ejecuta codeql test run java/tests/EmptyThen.

Cuando se ejecuta la prueba, hace lo siguiente:

  1. Buscar una prueba en el directorio EmptyThen.

  2. Extraer una base de datos de CodeQL de los archivos .java almacenados en el directorio EmptyThen.

  3. Compilar la consulta a la que hace referencia el archivo EmptyThen.qlref.

    Si se produce un error en este paso, se debe a que la CLI no encuentra el paquete personalizado de CodeQL. Vuelve a ejecutar el comando y especifica la ubicación del paquete personalizado de CodeQL como, por ejemplo:

    codeql test run --search-path=java java/tests/EmptyThen

    Para obtener información sobre cómo guardar la ruta de acceso de búsqueda como parte de la configuración, consulta "Especificación de opciones de comando en un archivo de configuración de CodeQL".

  4. Ejecutar la prueba ejecutando la consulta y generando un archivo de resultados EmptyThen.actual.

  5. Comprobar si hay un archivo EmptyThen.expected que se va a comparar con el archivo de resultados .actual.

  6. Notificar los resultados de la prueba; en este caso, un error: 0 tests passed; 1 tests failed:. La prueba ha producido un error porque aún no hemos agregado un archivo con los resultados esperados de la consulta.

Visualización de la salida de la prueba de consulta

CodeQL genera los archivos siguientes en el directorio EmptyThen:

  • EmptyThen.actual, un archivo que contiene los resultados reales que ha generado la consulta.
  • EmptyThen.testproj, una base de datos de prueba que puedes cargar en VS Code y usar para depurar pruebas con errores. Cuando las pruebas se completan correctamente, esta base de datos se elimina en un paso de mantenimiento. Para invalidar este paso, ejecuta test run con la opción --keep-databases.

En este caso, el error estaba previsto y es fácil de corregir. Si abres el archivo EmptyThen.actual, podrás ver los resultados de la prueba:


| Test.java:3:5:3:22 | stmt | This if statement has an empty then. |

Este archivo contiene una tabla, con una columna para la ubicación del resultado, junto con columnas independientes para cada parte de la cláusula select que genera la consulta. Dado que los resultados son los esperados, podemos actualizar la extensión de archivo a fin de definir esto como resultado esperado para esta prueba (EmptyThen.expected).

Si vuelves a ejecutar la prueba ahora, la salida será similar, pero finalizará notificando All 1 tests passed..

Si los resultados de la consulta cambian, por ejemplo, si revisas la instrucción select de la consulta, se producirá un error en la prueba. En el caso de los resultados con errores, la salida de la CLI incluye una diferencia unificada de los archivos EmptyThen.expected y EmptyThen.actual. Esta información puede ser suficiente para depurar errores de prueba triviales.

Para los errores que son más difíciles de depurar, puedes importar EmptyThen.testproj en CodeQL para VS Code, ejecutar EmptyThen.ql y ver los resultados en el código de ejemplo Test.java. Para obtener más información, vea «Administración de bases de datos de CodeQL».

Información adicional