@@ -48,7 +48,10 @@ def __init__(self, command, message):
48
48
# This regex captures ARG. ARG must not contain a right parenthesis, which
49
49
# terminates %dbg. ARG must not contain quotes, in which ARG might be enclosed
50
50
# during expansion.
51
- kPdbgRegex = '%dbg\\ (([^)\' "]*)\\ )'
51
+ #
52
+ # COMMAND that follows %dbg(ARG) is also captured. COMMAND can be
53
+ # empty as a result of conditinal substitution.
54
+ kPdbgRegex = '%dbg\\ (([^)\' "]*)\\ )(.*)'
52
55
53
56
class ShellEnvironment (object ):
54
57
@@ -899,7 +902,11 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
899
902
def executeScriptInternal (test , litConfig , tmpBase , commands , cwd ):
900
903
cmds = []
901
904
for i , ln in enumerate (commands ):
902
- ln = commands [i ] = re .sub (kPdbgRegex , ": '\\ 1'; " , ln )
905
+ match = re .match (kPdbgRegex , ln )
906
+ if match :
907
+ command = match .group (2 )
908
+ ln = commands [i ] = \
909
+ match .expand (": '\\ 1'; \\ 2" if command else ": '\\ 1'" )
903
910
try :
904
911
cmds .append (ShUtil .ShParser (ln , litConfig .isWindows ,
905
912
test .config .pipefail ).parse ())
@@ -987,15 +994,24 @@ def executeScript(test, litConfig, tmpBase, commands, cwd):
987
994
f = open (script , mode , ** open_kwargs )
988
995
if isWin32CMDEXE :
989
996
for i , ln in enumerate (commands ):
990
- commands [i ] = re .sub (kPdbgRegex , "echo '\\ 1' > nul && " , ln )
997
+ match = re .match (kPdbgRegex , ln )
998
+ if match :
999
+ command = match .group (2 )
1000
+ commands [i ] = \
1001
+ match .expand ("echo '\\ 1' > nul && " if command
1002
+ else "echo '\\ 1' > nul" )
991
1003
if litConfig .echo_all_commands :
992
1004
f .write ('@echo on\n ' )
993
1005
else :
994
1006
f .write ('@echo off\n ' )
995
1007
f .write ('\n @if %ERRORLEVEL% NEQ 0 EXIT\n ' .join (commands ))
996
1008
else :
997
1009
for i , ln in enumerate (commands ):
998
- commands [i ] = re .sub (kPdbgRegex , ": '\\ 1'; " , ln )
1010
+ match = re .match (kPdbgRegex , ln )
1011
+ if match :
1012
+ command = match .group (2 )
1013
+ commands [i ] = match .expand (": '\\ 1'; \\ 2" if command
1014
+ else ": '\\ 1'" )
999
1015
if test .config .pipefail :
1000
1016
f .write (b'set -o pipefail;' if mode == 'wb' else 'set -o pipefail;' )
1001
1017
if litConfig .echo_all_commands :
@@ -1179,7 +1195,8 @@ def memoized(x):
1179
1195
def _caching_re_compile (r ):
1180
1196
return re .compile (r )
1181
1197
1182
- def applySubstitutions (script , substitutions , recursion_limit = None ):
1198
+ def applySubstitutions (script , substitutions , conditions = {},
1199
+ recursion_limit = None ):
1183
1200
"""
1184
1201
Apply substitutions to the script. Allow full regular expression syntax.
1185
1202
Replace each matching occurrence of regular expression pattern a with
@@ -1193,14 +1210,103 @@ def applySubstitutions(script, substitutions, recursion_limit=None):
1193
1210
"""
1194
1211
1195
1212
# We use #_MARKER_# to hide %% while we do the other substitutions.
1196
- def escape (ln ):
1213
+ def escapePercents (ln ):
1197
1214
return _caching_re_compile ('%%' ).sub ('#_MARKER_#' , ln )
1198
1215
1199
- def unescape (ln ):
1216
+ def unescapePercents (ln ):
1200
1217
return _caching_re_compile ('#_MARKER_#' ).sub ('%' , ln )
1201
1218
1219
+ def substituteIfElse (ln ):
1220
+ # early exit to avoid wasting time on lines without
1221
+ # conditional substitutions
1222
+ if ln .find ('%if ' ) == - 1 :
1223
+ return ln
1224
+
1225
+ def tryParseIfCond (ln ):
1226
+ # space is important to not conflict with other (possible)
1227
+ # substitutions
1228
+ if not ln .startswith ('%if ' ):
1229
+ return None , ln
1230
+ ln = ln [4 :]
1231
+
1232
+ # stop at '%{'
1233
+ match = _caching_re_compile ('%{' ).search (ln )
1234
+ if not match :
1235
+ raise ValueError ("'%{' is missing for %if substitution" )
1236
+ cond = ln [:match .start ()]
1237
+
1238
+ # eat '%{' as well
1239
+ ln = ln [match .end ():]
1240
+ return cond , ln
1241
+
1242
+ def tryParseElse (ln ):
1243
+ match = _caching_re_compile ('^\s*%else\s*(%{)?' ).search (ln )
1244
+ if not match :
1245
+ return False , ln
1246
+ if not match .group (1 ):
1247
+ raise ValueError ("'%{' is missing for %else substitution" )
1248
+ return True , ln [match .end ():]
1249
+
1250
+ def tryParseEnd (ln ):
1251
+ if ln .startswith ('%}' ):
1252
+ return True , ln [2 :]
1253
+ return False , ln
1254
+
1255
+ def parseText (ln , isNested ):
1256
+ # parse everything until %if, or %} if we're parsing a
1257
+ # nested expression.
1258
+ match = _caching_re_compile (
1259
+ '(.*?)(?:%if|%})' if isNested else '(.*?)(?:%if)' ).search (ln )
1260
+ if not match :
1261
+ # there is no terminating pattern, so treat the whole
1262
+ # line as text
1263
+ return ln , ''
1264
+ text_end = match .end (1 )
1265
+ return ln [:text_end ], ln [text_end :]
1266
+
1267
+ def parseRecursive (ln , isNested ):
1268
+ result = ''
1269
+ while len (ln ):
1270
+ if isNested :
1271
+ found_end , _ = tryParseEnd (ln )
1272
+ if found_end :
1273
+ break
1274
+
1275
+ # %if cond %{ branch_if %} %else %{ branch_else %}
1276
+ cond , ln = tryParseIfCond (ln )
1277
+ if cond :
1278
+ branch_if , ln = parseRecursive (ln , isNested = True )
1279
+ found_end , ln = tryParseEnd (ln )
1280
+ if not found_end :
1281
+ raise ValueError ("'%}' is missing for %if substitution" )
1282
+
1283
+ branch_else = ''
1284
+ found_else , ln = tryParseElse (ln )
1285
+ if found_else :
1286
+ branch_else , ln = parseRecursive (ln , isNested = True )
1287
+ found_end , ln = tryParseEnd (ln )
1288
+ if not found_end :
1289
+ raise ValueError ("'%}' is missing for %else substitution" )
1290
+
1291
+ if BooleanExpression .evaluate (cond , conditions ):
1292
+ result += branch_if
1293
+ else :
1294
+ result += branch_else
1295
+ continue
1296
+
1297
+ # The rest is handled as plain text.
1298
+ text , ln = parseText (ln , isNested )
1299
+ result += text
1300
+
1301
+ return result , ln
1302
+
1303
+ result , ln = parseRecursive (ln , isNested = False )
1304
+ assert len (ln ) == 0
1305
+ return result
1306
+
1202
1307
def processLine (ln ):
1203
1308
# Apply substitutions
1309
+ ln = substituteIfElse (escapePercents (ln ))
1204
1310
for a ,b in substitutions :
1205
1311
if kIsWindows :
1206
1312
b = b .replace ("\\ " ,"\\ \\ " )
@@ -1211,7 +1317,7 @@ def processLine(ln):
1211
1317
# short-lived, since the set of substitutions is fairly small, and
1212
1318
# since thrashing has such bad consequences, not bounding the cache
1213
1319
# seems reasonable.
1214
- ln = _caching_re_compile (a ).sub (str (b ), escape (ln ))
1320
+ ln = _caching_re_compile (a ).sub (str (b ), escapePercents (ln ))
1215
1321
1216
1322
# Strip the trailing newline and any extra whitespace.
1217
1323
return ln .strip ()
@@ -1235,7 +1341,7 @@ def processLineToFixedPoint(ln):
1235
1341
1236
1342
process = processLine if recursion_limit is None else processLineToFixedPoint
1237
1343
1238
- return [unescape (process (ln )) for ln in script ]
1344
+ return [unescapePercents (process (ln )) for ln in script ]
1239
1345
1240
1346
1241
1347
class ParserKind (object ):
@@ -1610,7 +1716,8 @@ def executeShTest(test, litConfig, useExternalSh,
1610
1716
substitutions = list (extra_substitutions )
1611
1717
substitutions += getDefaultSubstitutions (test , tmpDir , tmpBase ,
1612
1718
normalize_slashes = useExternalSh )
1613
- script = applySubstitutions (script , substitutions ,
1719
+ conditions = { feature : True for feature in test .config .available_features }
1720
+ script = applySubstitutions (script , substitutions , conditions ,
1614
1721
recursion_limit = test .config .recursiveExpansionLimit )
1615
1722
1616
1723
return _runShTest (test , litConfig , useExternalSh , script , tmpBase )
0 commit comments