@@ -48,7 +48,10 @@ def __init__(self, command, message):
4848# This regex captures ARG. ARG must not contain a right parenthesis, which
4949# terminates %dbg. ARG must not contain quotes, in which ARG might be enclosed
5050# 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\\ (([^)\' "]*)\\ )(.*)'
5255
5356class ShellEnvironment (object ):
5457
@@ -899,7 +902,11 @@ def _executeShCmd(cmd, shenv, results, timeoutHelper):
899902def executeScriptInternal (test , litConfig , tmpBase , commands , cwd ):
900903 cmds = []
901904 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'" )
903910 try :
904911 cmds .append (ShUtil .ShParser (ln , litConfig .isWindows ,
905912 test .config .pipefail ).parse ())
@@ -987,15 +994,24 @@ def executeScript(test, litConfig, tmpBase, commands, cwd):
987994 f = open (script , mode , ** open_kwargs )
988995 if isWin32CMDEXE :
989996 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" )
9911003 if litConfig .echo_all_commands :
9921004 f .write ('@echo on\n ' )
9931005 else :
9941006 f .write ('@echo off\n ' )
9951007 f .write ('\n @if %ERRORLEVEL% NEQ 0 EXIT\n ' .join (commands ))
9961008 else :
9971009 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'" )
9991015 if test .config .pipefail :
10001016 f .write (b'set -o pipefail;' if mode == 'wb' else 'set -o pipefail;' )
10011017 if litConfig .echo_all_commands :
@@ -1179,7 +1195,8 @@ def memoized(x):
11791195def _caching_re_compile (r ):
11801196 return re .compile (r )
11811197
1182- def applySubstitutions (script , substitutions , recursion_limit = None ):
1198+ def applySubstitutions (script , substitutions , conditions = {},
1199+ recursion_limit = None ):
11831200 """
11841201 Apply substitutions to the script. Allow full regular expression syntax.
11851202 Replace each matching occurrence of regular expression pattern a with
@@ -1193,14 +1210,103 @@ def applySubstitutions(script, substitutions, recursion_limit=None):
11931210 """
11941211
11951212 # We use #_MARKER_# to hide %% while we do the other substitutions.
1196- def escape (ln ):
1213+ def escapePercents (ln ):
11971214 return _caching_re_compile ('%%' ).sub ('#_MARKER_#' , ln )
11981215
1199- def unescape (ln ):
1216+ def unescapePercents (ln ):
12001217 return _caching_re_compile ('#_MARKER_#' ).sub ('%' , ln )
12011218
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+
12021307 def processLine (ln ):
12031308 # Apply substitutions
1309+ ln = substituteIfElse (escapePercents (ln ))
12041310 for a ,b in substitutions :
12051311 if kIsWindows :
12061312 b = b .replace ("\\ " ,"\\ \\ " )
@@ -1211,7 +1317,7 @@ def processLine(ln):
12111317 # short-lived, since the set of substitutions is fairly small, and
12121318 # since thrashing has such bad consequences, not bounding the cache
12131319 # seems reasonable.
1214- ln = _caching_re_compile (a ).sub (str (b ), escape (ln ))
1320+ ln = _caching_re_compile (a ).sub (str (b ), escapePercents (ln ))
12151321
12161322 # Strip the trailing newline and any extra whitespace.
12171323 return ln .strip ()
@@ -1235,7 +1341,7 @@ def processLineToFixedPoint(ln):
12351341
12361342 process = processLine if recursion_limit is None else processLineToFixedPoint
12371343
1238- return [unescape (process (ln )) for ln in script ]
1344+ return [unescapePercents (process (ln )) for ln in script ]
12391345
12401346
12411347class ParserKind (object ):
@@ -1610,7 +1716,8 @@ def executeShTest(test, litConfig, useExternalSh,
16101716 substitutions = list (extra_substitutions )
16111717 substitutions += getDefaultSubstitutions (test , tmpDir , tmpBase ,
16121718 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 ,
16141721 recursion_limit = test .config .recursiveExpansionLimit )
16151722
16161723 return _runShTest (test , litConfig , useExternalSh , script , tmpBase )
0 commit comments