Skip to content

Commit da62e81

Browse files
committed
Shared: Post-processing query for inline test expectations
1 parent 97863be commit da62e81

File tree

2 files changed

+135
-8
lines changed

2 files changed

+135
-8
lines changed

shared/util/codeql/util/test/InlineExpectationsTest.qll

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,20 @@ module Make<InlineExpectationsTestSig Impl> {
134134
* predicate for an active test will be ignored. This makes it possible to write multiple tests in
135135
* different `.ql` files that all query the same source code.
136136
*/
137+
bindingset[result]
137138
string getARelevantTag();
138139

140+
/**
141+
* Holds if expected tag `expectedTag` matches actual tag `actualTag`.
142+
*
143+
* This is normally defined as `expectedTag = actualTag`.
144+
*/
145+
bindingset[expectedTag, actualTag]
146+
default predicate tagMatches(string expectedTag, string actualTag) { expectedTag = actualTag }
147+
148+
bindingset[expectedTag]
149+
default predicate tagIsOptional(string expectedTag) { none() }
150+
139151
/**
140152
* Returns the actual results of the query that is being tested. Each result consist of the
141153
* following values:
@@ -200,13 +212,13 @@ module Make<InlineExpectationsTestSig Impl> {
200212
not exists(ActualTestResult actualResult | expectation.matchesActualResult(actualResult)) and
201213
expectation.getTag() = TestImpl::getARelevantTag() and
202214
element = expectation and
203-
(
204-
expectation instanceof GoodTestExpectation and
205-
message = "Missing result: " + expectation.getExpectationText()
206-
or
207-
expectation instanceof FalsePositiveTestExpectation and
208-
message = "Fixed spurious result: " + expectation.getExpectationText()
209-
)
215+
not expectation.isOptional()
216+
|
217+
expectation instanceof GoodTestExpectation and
218+
message = "Missing result: " + expectation.getExpectationText()
219+
or
220+
expectation instanceof FalsePositiveTestExpectation and
221+
message = "Fixed spurious result: " + expectation.getExpectationText()
210222
)
211223
or
212224
exists(InvalidTestExpectation expectation |
@@ -311,9 +323,11 @@ module Make<InlineExpectationsTestSig Impl> {
311323

312324
predicate matchesActualResult(ActualTestResult actualResult) {
313325
onSameLine(pragma[only_bind_into](this), actualResult) and
314-
this.getTag() = actualResult.getTag() and
326+
TestImpl::tagMatches(this.getTag(), actualResult.getTag()) and
315327
this.getValue() = actualResult.getValue()
316328
}
329+
330+
predicate isOptional() { TestImpl::tagIsOptional(tag) }
317331
}
318332

319333
// Note: These next three classes correspond to all the possible values of type `TColumn`.
@@ -385,6 +399,7 @@ module Make<InlineExpectationsTestSig Impl> {
385399
* ```
386400
*/
387401
module MergeTests<TestSig TestImpl1, TestSig TestImpl2> implements TestSig {
402+
bindingset[result]
388403
string getARelevantTag() {
389404
result = TestImpl1::getARelevantTag() or result = TestImpl2::getARelevantTag()
390405
}
@@ -408,6 +423,7 @@ module Make<InlineExpectationsTestSig Impl> {
408423
module MergeTests3<TestSig TestImpl1, TestSig TestImpl2, TestSig TestImpl3> implements TestSig {
409424
private module M = MergeTests<MergeTests<TestImpl1, TestImpl2>, TestImpl3>;
410425

426+
bindingset[result]
411427
string getARelevantTag() { result = M::getARelevantTag() }
412428

413429
predicate hasActualResult(Impl::Location location, string element, string tag, string value) {
@@ -427,6 +443,7 @@ module Make<InlineExpectationsTestSig Impl> {
427443
{
428444
private module M = MergeTests<MergeTests3<TestImpl1, TestImpl2, TestImpl3>, TestImpl4>;
429445

446+
bindingset[result]
430447
string getARelevantTag() { result = M::getARelevantTag() }
431448

432449
predicate hasActualResult(Impl::Location location, string element, string tag, string value) {
@@ -448,6 +465,7 @@ module Make<InlineExpectationsTestSig Impl> {
448465
private module M =
449466
MergeTests<MergeTests4<TestImpl1, TestImpl2, TestImpl3, TestImpl4>, TestImpl5>;
450467

468+
bindingset[result]
451469
string getARelevantTag() { result = M::getARelevantTag() }
452470

453471
predicate hasActualResult(Impl::Location location, string element, string tag, string value) {
@@ -590,3 +608,105 @@ private string expectationPattern() {
590608
result = tags + "(?:=" + value + ")?"
591609
)
592610
}
611+
612+
/**
613+
* Holds if alerts should be restricted to those whose alert messages
614+
* that regexp matches `alertMessage`.
615+
*/
616+
extensible predicate testPostProcessingRestrictAlertsTo(string alertMessage);
617+
618+
/**
619+
* Provides logic for creating a `@kind test-postprocess` query that checks
620+
* inline test expectations using `$ Alert` markers.
621+
*/
622+
module TestPostProcessing {
623+
external predicate queryResults(string relation, int row, int column, string data);
624+
625+
external predicate queryRelations(string relation);
626+
627+
signature module InputSig<InlineExpectationsTestSig Input> {
628+
string getRelativeUrl(Input::Location location);
629+
}
630+
631+
module Make<InlineExpectationsTestSig Input, InputSig<Input> Input2> {
632+
private import InlineExpectationsTest as InlineExpectationsTest
633+
private import InlineExpectationsTest::Make<Input>
634+
635+
private module TestInput implements TestSig {
636+
bindingset[result]
637+
string getARelevantTag() { any() }
638+
639+
bindingset[expectedTag, actualTag]
640+
predicate tagMatches(string expectedTag, string actualTag) {
641+
expectedTag = "Alert"
642+
or
643+
exists(string alertTagRegex, string s |
644+
alertTagRegex = "Alert\\[(.*)\\]" and
645+
s = expectedTag.regexpCapture(alertTagRegex, 1) and
646+
actualTag.regexpMatch(".*" + s + ".*")
647+
)
648+
}
649+
650+
bindingset[expectedTag]
651+
predicate tagIsOptional(string expectedTag) {
652+
not expectedTag.regexpMatch("Alert(\\[.*\\])?")
653+
or
654+
exists(string alertTagRegex, string s, string alertMessage |
655+
alertTagRegex = "Alert\\[(.*)\\]" and
656+
s = expectedTag.regexpCapture(alertTagRegex, 1) and
657+
testPostProcessingRestrictAlertsTo(alertMessage) and
658+
not alertMessage.regexpMatch(".*" + s + ".*")
659+
)
660+
}
661+
662+
predicate hasActualResult(Input::Location location, string element, string tag, string value) {
663+
exists(int row, string loc |
664+
queryResults("#select", row, 0, loc) and
665+
queryResults("#select", row, 2, element) and
666+
tag = element and
667+
value = "" and
668+
Input2::getRelativeUrl(location) = loc
669+
)
670+
}
671+
}
672+
673+
private module Test = MakeTest<TestInput>;
674+
675+
private newtype TTestFailure =
676+
MkTestFailure(Test::FailureLocatable f, string message) { Test::testFailures(f, message) }
677+
678+
private predicate rankedTestFailures(int i, MkTestFailure f) {
679+
f =
680+
rank[i](MkTestFailure f0, Test::FailureLocatable fl, string message, string filename,
681+
int startLine, int startColumn, int endLine, int endColumn |
682+
f0 = MkTestFailure(fl, message) and
683+
fl.getLocation().hasLocationInfo(filename, startLine, startColumn, endLine, endColumn)
684+
|
685+
f0 order by filename, startLine, startColumn, endLine, endColumn, message
686+
)
687+
}
688+
689+
query predicate results(string relation, int row, int column, string data) {
690+
queryResults(relation, row, column, data)
691+
or
692+
exists(MkTestFailure f, Test::FailureLocatable fl, string message |
693+
relation = "testFailures" and
694+
rankedTestFailures(row, f) and
695+
f = MkTestFailure(fl, message)
696+
|
697+
column = 0 and data = Input2::getRelativeUrl(fl.getLocation())
698+
or
699+
column = 1 and data = fl.toString()
700+
or
701+
column = 2 and data = message
702+
)
703+
}
704+
705+
query predicate resultRelations(string relation) {
706+
queryRelations(relation)
707+
or
708+
Test::testFailures(_, _) and
709+
relation = "testFailures"
710+
}
711+
}
712+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Make sure that the `testPostProcessingRestrictAlertsTo` predicate has at least one definition
2+
# to avoid errors about undefined extensionals.
3+
extensions:
4+
- addsTo:
5+
pack: codeql/util
6+
extensible: testPostProcessingRestrictAlertsTo
7+
data: []

0 commit comments

Comments
 (0)