Skip to main content

Testen benutzerdefinierter Abfragen

Du kannst Tests für deine CodeQL-Abfragen einrichten, um sicherzustellen, dass diese weiterhin die erwarteten Ergebnisse mit neuen Releases der CodeQL CLI zurückgeben.

Wer kann dieses Feature verwenden?

CodeQL ist für die folgenden Repositorytypen verfügbar:

Informationen zum Testen benutzerdefinierter Abfragen

CodeQL enthält ein einfaches Testframework für automatisierte Regressionstests von Abfragen. Teste deine Abfragen, um sicherzustellen, dass sie sich erwartungsgemäß verhalten.

Während eines Abfragetests vergleicht CodeQL die Ergebnisse, die der oder die Benutzer*in von der Abfrage erwartet, mit den tatsächlich generierten Ergebnissen. Wenn die erwarteten und tatsächlichen Ergebnisse voneinander abweichen, schlägt der Abfragetest fehl. Um den Test zu korrigieren, solltest du die Abfrage und die erwarteten Ergebnisse durchlaufen, bis die tatsächlichen Ergebnisse und die erwarteten Ergebnisse genau übereinstimmen. In diesem Artikel erfährst du, wie du mithilfe des Unterbefehls test run Testdateien erstellst und Tests für diese ausführst.

Einrichten eines CodeQL-Testpakets für benutzerdefinierte Abfragen

Alle CodeQL-Tests müssen in einem speziellen CodeQL-Testpaket gespeichert werden. Dabei handelt es sich um ein Verzeichnis für Testdateien mit einer qlpack.yml-Datei, die Folgendes definiert:

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

Der dependencies-Wert gibt die CodeQL-Pakete an, die zu testende Abfragen enthalten. In der Regel werden diese Pakete über die Quelle aufgelöst, sodass es nicht erforderlich ist, eine feste Version des Pakets anzugeben. Mit extractor wird definiert, welche Sprache die CLI zum Erstellen von Testdatenbanken aus den Codedateien verwendet, die in diesem CodeQL-Paket gespeichert sind. Weitere Informationen findest du unter Anpassen der Analyse mit CodeQL-Paketen.

Es kann hilfreich sein, sich die Organisation der Abfragetests im CodeQL-Repository anzusehen. Jede Sprache verfügt über ein src-Verzeichnis, ql/<language>/ql/src, das Bibliotheken und Abfragen zum Analysieren von Codebases enthält. Neben dem src-Verzeichnis gibt es ein test-Verzeichnis mit Tests für diese Bibliotheken und Abfragen.

Jedes test-Verzeichnis ist als CodeQL-Testpaket mit zwei Unterverzeichnissen konfiguriert:

  • query-tests enthält Unterverzeichnisse mit Tests für Abfragen, die im src-Verzeichnis gespeichert sind. Jedes Unterverzeichnis enthält Testcode und eine QL-Referenzdatei, die die zu testende Abfrage angibt.
  • library-tests enthält Unterverzeichnisse mit Tests für QL-Bibliotheksdateien. Jedes Unterverzeichnis enthält Testcode und Abfragen, die als Komponententests für eine Bibliothek geschrieben wurden.

Nachdem du die qlpack.yml-Datei erstellt hast, musst du sicherstellen, dass alle Abhängigkeiten heruntergeladen und für die CLI verfügbar sind. Führe hierzu den folgenden Befehl im selben Verzeichnis wie die Datei qlpack.yml aus:

codeql pack install

Dadurch wird eine codeql-pack.lock.yml-Datei generiert, die alle transitiven Abhängigkeiten angibt, die zum Ausführen von Abfragen in diesem Paket erforderlich sind. Diese Datei sollte in die Quellcodeverwaltung eingecheckt werden.

Einrichten der Testdateien für eine Abfrage

Für jede Abfrage, die du testen möchtest, solltest du ein Unterverzeichnis im CodeQL-Testpaket erstellen. Füge dann dem Unterverzeichnis die folgenden Dateien hinzu, bevor du den Testbefehl ausführst:

  • Eine Abfrageverweisdatei (.qlref-Datei), die den Speicherort der zu testenden Abfrage definiert. Der Speicherort wird relativ zum Stamm des CodeQL-Pakets definiert, das die Abfrage enthält. In der Regel handelt es sich dabei um ein CodeQL-Paket, das im dependencies-Block des Testpakets angegeben ist. Weitere Informationen findest du unter Abfragereferenzdateien.

    Du musst keine Abfrageverweisdatei hinzufügen, wenn die Abfrage, die du testen möchtest, im Testverzeichnis gespeichert ist. Es empfiehlt sich jedoch im Allgemeinen, Abfragen getrennt von Tests zu speichern. Die einzige Ausnahme sind Komponententests für QL-Bibliotheken, die in der Regel in Testpaketen gespeichert werden, getrennt von Abfragen, die Warnungen oder Pfade generieren.

  • Der Beispielcode, für den du deine Abfrage ausführen möchtest. Dieser sollte aus einer oder mehreren Dateien bestehen, die Beispiele für den Code enthalten, den die Abfrage ermitteln soll.

Du kannst auch die Ergebnisse definieren, die beim Ausführen der Abfrage für den Beispielcode erwartet werden, indem du eine Datei mit der Erweiterung .expectederstellst. Alternativ kannst du den Testbefehl beibehalten, um die .expected-Datei für dich zu erstellen.

Ein Beispiel zum Erstellen und Testen einer Abfrage findest du im folgenden Beispiel.

Hinweis: Deine .ql-, .qlref- und .expected-Dateien müssen konsistente Namen aufweisen:

  • Wenn du die .ql-Datei direkt im Testbefehl angeben möchtest, muss sie denselben Basisnamen wie die zugehörige .expected-Datei aufweisen. Wenn die Abfrage beispielsweise MyJavaQuery.ql lautet, muss die erwartete Ergebnisdatei MyJavaQuery.expected heißen.

  • Wenn du eine .qlref-Datei im Befehl angeben möchtest, muss sie den gleichen Basisnamen wie die zugehörige .expected-Datei aufweisen, aber die Abfrage selbst kann einen anderen Namen haben.

  • Die Namen der Beispielcodedateien müssen nicht mit den anderen Testdateien konsistent sein. Alle Beispielcodedateien neben der .qlref- oder .ql-Datei und in allen Unterverzeichnissen werden verwendet, um eine Testdatenbank zu erstellen. Daher wird der Einfachheit halber empfohlen, Testdateien nicht in Verzeichnissen zu speichern, die Vorgänger voneinander sind.

Wird ausgeführt codeql test run

CodeQL-Abfragetests werden mit dem folgenden Befehl ausgeführt:

codeql test run <test|dir>

Das <test|dir>-Argument kann aus einem oder mehreren der folgenden Elemente bestehen:

  • Pfad zu einer .ql-Datei
  • Pfad zu einer .qlref-Datei, die auf eine .ql-Datei verweist
  • Pfad zu einem Verzeichnis, das rekursiv nach .ql- und .qlref-Dateien durchsucht wird

Du kannst auch Folgendes angeben:

  • --threads:: Hiermit gibst du optional die Anzahl der beim Ausführen von Abfragen zu verwendenden Threads an. Die Standardoption ist 1. Du kannst weitere Threads angeben, um die Abfrageausführung zu beschleunigen. Wenn du 0 angibst, wird die Anzahl der Threads auf die Anzahl der logischen Prozessoren festgelegt.

Ausführliche Informationen zu allen Optionen, die du beim Testen von Abfragen verwenden kannst, findest du unter test run.

Beispiel

Das folgende Beispiel zeigt, wie du einen Test für eine Abfrage einrichtest, die Java-Code nach if-Anweisungen durchsucht, die leere then-Blöcke enthalten. Es enthält Schritte zum Hinzufügen der benutzerdefinierten Abfrage und der entsprechenden Testdateien, um CodeQL-Pakete vom Check-Out des CodeQL-Repositorys zu trennen. Dadurch wird sichergestellt, dass benutzerdefinierte Abfragen und Testes nicht überschrieben werden, wenn du die CodeQL-Bibliotheken aktualisierst oder einen anderen Branch auscheckst.

Vorbereiten der Abfrage und Testdateien

  1. Schreibe die Abfrage. Die folgende einfache Abfrage ermittelt beispielsweise leere then-Blöcke in Java-Code:

    import java
    
    from IfStmt ifstmt
    where ifstmt.getThen() instanceof EmptyStmt
    select ifstmt, "This if statement has an empty then."
    
  2. Speichere die Abfrage in einer Datei namens EmptyThen.ql in dem Verzeichnis mit deinen anderen benutzerdefinierten Abfragen. Beispiel: custom-queries/java/queries/EmptyThen.ql.

  3. Wenn du deine benutzerdefinierten Abfragen noch nicht zu einem CodeQL-Paket hinzugefügt hast, erstelle jetzt ein CodeQL-Paket. Wenn deine benutzerdefinierten Java-Abfragen beispielsweise unter custom-queries/java/queries gespeichert sind, füge eine qlpack.yml-Datei mit dem folgenden Inhalt zu custom-queries/java/queries hinzu:

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

    Weitere Informationen zu CodeQL-Paketen findest du unter Anpassen der Analyse mit CodeQL-Paketen.

  4. Erstelle ein CodeQL-Paket für deine Java-Tests, indem du eine qlpack.yml-Datei mit dem folgenden Inhalt zu custom-queries/java/tests hinzufügst. Passe hierbei dependencies an den Namen deines CodeQL-Pakets für benutzerdefinierte Abfragen an:

    Die folgende qlpack.yml-Datei gibt an, dass my-github-user/my-query-tests von my-github-user/my-custom-queries mit einer Version größer oder gleich 1.2.3 und niedriger als 2.0.0 abhängig ist. Außerdem wurde deklariert, dass die CLI den extractor von Java beim Erstellen von Testdatenbanken verwenden soll. Die tests: .-Zeile deklariert, dass alle .ql-Dateien im Paket als Tests ausgeführt werden sollen, wenn codeql test run mit der --strict-test-discovery-Option ausgeführt wird. In der Regel enthalten Testpakete keine version-Eigenschaft. Dadurch wird verhindert, dass diese versehentlich veröffentlicht werden.

    name: my-github-user/my-query-tests
    dependencies:
      my-github-user/my-custom-queries: ^1.2.3
    extractor: java-kotlin
    tests: .
    
  5. Führe codeql pack install im Stammverzeichnis des Testverzeichnisses aus. Dadurch wird eine codeql-pack.lock.yml-Datei generiert, die alle transitiven Abhängigkeiten angibt, die zum Ausführen von Abfragen in diesem Paket erforderlich sind.

  6. Erstelle im Java-Testpaket ein Verzeichnis, das die Testdateien enthält, die EmptyThen.ql zugeordnet sind. Beispiel: custom-queries/java/tests/EmptyThen.

  7. Erstelle EmptyThen.qlref im neuen Verzeichnis, um den Speicherort von EmptyThen.ql zu definieren. Der Pfad zur Abfrage muss relativ zum Stamm des CodeQL-Pakets angegeben werden, das die Abfrage enthält. In diesem Fall befindet sich die Abfrage im obersten Verzeichnis des CodeQL-Pakets mit dem Namen my-custom-queries, das als Abhängigkeit für my-query-tests deklariert wird. Daher sollte EmptyThen.qlref nur EmptyThen.ql enthalten.

  8. Erstelle ein Codeschnipsel zum Testen. Der folgende Java-Code enthält eine leere if-Anweisung in der dritten Zeile. Speichere es unter 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");
        }
      }
    }
    

Ausführen des Tests

Wechsle zum Ausführen des Tests in das Verzeichnis custom-queries, und führe codeql test run java/tests/EmptyThen aus.

Wenn der Test ausgeführt wird, geschieht Folgendes:

  1. Ein Test wird im Verzeichnis EmptyThen gefunden.

  2. Eine CodeQL-Datenbank wird aus den .java-Dateien im EmptyThen-Verzeichnis extrahiert.

  3. Die Abfrage wird kompiliert, auf die in der Datei EmptyThen.qlref verwiesen wird.

    Wenn dieser Schritt fehlschlägt, liegt das daran, dass die CLI dein benutzerdefiniertes CodeQL-Paket nicht finden kann. Führe den Befehl erneut aus, und gib den Speicherort deines benutzerdefinierten CodeQL-Pakets an, z. B.:

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

    Informationen zum Speichern des Suchpfads als Teil deiner Konfiguration findest du unter Angeben von Befehlsoptionen in einer CodeQL-Konfigurationsdatei.

  4. Der Test wird ausgeführt, indem die Abfrage ausgeführt und eine EmptyThen.actual-Ergebnisdatei generiert wird.

  5. Es wird nach einer EmptyThen.expected-Datei gesucht, die mit der .actual-Ergebnisdatei verglichen werden kann.

  6. Die Ergebnisse des Tests werden zurückgegeben, in diesem Fall ein Misserfolg: 0 tests passed; 1 tests failed:. Der Test war nicht erfolgreich, da noch keine Datei mit den erwarteten Ergebnissen zur Abfrage hinzugefügt wurde.

Anzeigen der Abfragetestausgabe

CodeQL generiert die folgenden Dateien im EmptyThen-Verzeichnis:

  • EmptyThen.actual, eine Datei, die die tatsächlichen Ergebnisse enthält, die von der Abfrage generiert wurden
  • EmptyThen.testproj, eine Testdatenbank, die du in VS Code laden und zum Debuggen erfolgloser Tests verwenden kannst. Wenn die Tests erfolgreich abgeschlossen wurden, wird diese Datenbank in einem Housekeepingschritt gelöscht. Du kannst diesen Schritt außer Kraft setzen, indem du test run mit der Option --keep-databases ausführst.

In diesem Fall wurde der Fehler erwartet, und dieser lässt sich leicht beheben. Wenn du die EmptyThen.actual-Datei öffnest, siehst du die Ergebnisse des Tests:


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

Diese Datei enthält eine Tabelle mit einer Spalte für den Speicherort des Ergebnisses sowie separate Spalten für jeden Teil der select-Klausel, den die Abfrage ausgibt. Da das Ergebnis den Erwartungen entspricht, kann die Dateierweiterung aktualisiert werden, um es als erwartetes Ergebnis für diesen Test (EmptyThen.expected) zu definieren.

Wenn du den Test jetzt erneut ausführst, ist die Ausgabe ähnlich, aber sie endet mit der Meldung: All 1 tests passed..

Wenn sich die Ergebnisse der Abfrage ändern, z. B. durch eine Überarbeitung der select-Anweisung der Abfrage, ist der Test nicht erfolgreich. Bei Misserfolgen enthält die CLI-Ausgabe einen vereinheitlichten Unterschied zwischen der EmptyThen.expected- und EmptyThen.actual-Datei. Diese Informationen können ausreichen, um triviale Testfehler zu debuggen.

Bei schwieriger zu debuggenden Fehlern kannst du EmptyThen.testproj in CodeQL für VS Code importieren, EmptyThen.ql ausführen und dir die Ergebnisse im Test.java-Beispielcode ansehen. Weitere Informationen findest du unter Verwalten von CodeQL-Datenbanken.

Weitere Informationen