@@ -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+ }
0 commit comments