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 imsrc
-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 imdependencies
-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 .expected
erstellst. 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.
Note
Deine .ql
-, .qlref
- und .expected
-Dateien müssen einheitliche 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 beispielsweiseMyJavaQuery.ql
lautet, muss die erwartete ErgebnisdateiMyJavaQuery.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 ist1
. Du kannst weitere Threads angeben, um die Abfrageausführung zu beschleunigen. Wenn du0
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
-
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."
-
Speichere die Abfrage in einer Datei namens
EmptyThen.ql
in dem Verzeichnis mit deinen anderen benutzerdefinierten Abfragen. Beispiel:custom-queries/java/queries/EmptyThen.ql
. -
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 eineqlpack.yml
-Datei mit dem folgenden Inhalt zucustom-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.
-
Erstelle ein CodeQL-Paket für deine Java-Tests, indem du eine
qlpack.yml
-Datei mit dem folgenden Inhalt zucustom-queries/java/tests
hinzufügst. Passe hierbeidependencies
an den Namen deines CodeQL-Pakets für benutzerdefinierte Abfragen an:Die folgende
qlpack.yml
-Datei gibt an, dassmy-github-user/my-query-tests
vonmy-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 denextractor
von Java beim Erstellen von Testdatenbanken verwenden soll. Dietests: .
-Zeile deklariert, dass alle.ql
-Dateien im Paket als Tests ausgeführt werden sollen, wenncodeql test run
mit der--strict-test-discovery
-Option ausgeführt wird. In der Regel enthalten Testpakete keineversion
-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 tests: .
-
Führe
codeql pack install
im Stammverzeichnis des Testverzeichnisses aus. Dadurch wird einecodeql-pack.lock.yml
-Datei generiert, die alle transitiven Abhängigkeiten angibt, die zum Ausführen von Abfragen in diesem Paket erforderlich sind. -
Erstelle im Java-Testpaket ein Verzeichnis, das die Testdateien enthält, die
EmptyThen.ql
zugeordnet sind. Beispiel:custom-queries/java/tests/EmptyThen
. -
Erstelle
EmptyThen.qlref
im neuen Verzeichnis, um den Speicherort vonEmptyThen.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 Namenmy-custom-queries
, das als Abhängigkeit fürmy-query-tests
deklariert wird. Daher sollteEmptyThen.qlref
nurEmptyThen.ql
enthalten. -
Erstelle ein Codeschnipsel zum Testen. Der folgende Java-Code enthält eine leere
if
-Anweisung in der dritten Zeile. Speichere es untercustom-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:
-
Ein Test wird im Verzeichnis
EmptyThen
gefunden. -
Eine CodeQL-Datenbank wird aus den
.java
-Dateien imEmptyThen
-Verzeichnis extrahiert. -
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.
-
Der Test wird ausgeführt, indem die Abfrage ausgeführt und eine
EmptyThen.actual
-Ergebnisdatei generiert wird. -
Es wird nach einer
EmptyThen.expected
-Datei gesucht, die mit der.actual
-Ergebnisdatei verglichen werden kann. -
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 wurdenEmptyThen.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 dutest 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.