1 # Copyright 2012 the V8 project authors. All rights reserved. 2 # Redistribution and use in source and binary forms, with or without 3 # modification, are permitted provided that the following conditions are 4 # met: 5 # 6 # * Redistributions of source code must retain the above copyright 7 # notice, this list of conditions and the following disclaimer. 8 # * Redistributions in binary form must reproduce the above 9 # copyright notice, this list of conditions and the following 10 # disclaimer in the documentation and/or other materials provided 11 # with the distribution. 12 # * Neither the name of Google Inc. nor the names of its 13 # contributors may be used to endorse or promote products derived 14 # from this software without specific prior written permission. 15 # 16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 28 29 import cStringIO 30 import re 31 32 # These outcomes can occur in a TestCase's outcomes list: 33 SKIP = 'SKIP' 34 FAIL = 'FAIL' 35 PASS = 'PASS' 36 OKAY = 'OKAY' 37 TIMEOUT = 'TIMEOUT' 38 CRASH = 'CRASH' 39 SLOW = 'SLOW' 40 FLAKY = 'FLAKY' 41 # These are just for the status files and are mapped below in DEFS: 42 FAIL_OK = 'FAIL_OK' 43 PASS_OR_FAIL = 'PASS_OR_FAIL' 44 45 KEYWORDS = {SKIP: SKIP, 46 FAIL: FAIL, 47 PASS: PASS, 48 OKAY: OKAY, 49 TIMEOUT: TIMEOUT, 50 CRASH: CRASH, 51 SLOW: SLOW, 52 FLAKY: FLAKY, 53 FAIL_OK: FAIL_OK, 54 PASS_OR_FAIL: PASS_OR_FAIL} 55 56 class Expression(object): 57 pass 58 59 60 class Constant(Expression): 61 62 def __init__(self, value): 63 self.value = value 64 65 def Evaluate(self, env, defs): 66 return self.value 67 68 69 class Variable(Expression): 70 71 def __init__(self, name): 72 self.name = name 73 74 def GetOutcomes(self, env, defs): 75 if self.name in env: return set([env[self.name]]) 76 else: return set([]) 77 78 def Evaluate(self, env, defs): 79 return env[self.name] 80 81 def __str__(self): 82 return self.name 83 84 def string(self, logical): 85 return self.__str__() 86 87 88 class Outcome(Expression): 89 90 def __init__(self, name): 91 self.name = name 92 93 def GetOutcomes(self, env, defs): 94 if self.name in defs: 95 return defs[self.name].GetOutcomes(env, defs) 96 else: 97 return set([self.name]) 98 99 def __str__(self): 100 if self.name in KEYWORDS: 101 return "%s" % KEYWORDS[self.name] 102 return "'%s'" % self.name 103 104 def string(self, logical): 105 if logical: 106 return "%s" % self.name 107 return self.__str__() 108 109 110 class Operation(Expression): 111 112 def __init__(self, left, op, right): 113 self.left = left 114 self.op = op 115 self.right = right 116 117 def Evaluate(self, env, defs): 118 if self.op == '||' or self.op == ',': 119 return self.left.Evaluate(env, defs) or self.right.Evaluate(env, defs) 120 elif self.op == 'if': 121 return False 122 elif self.op == '==': 123 return not self.left.GetOutcomes(env, defs).isdisjoint(self.right.GetOutcomes(env, defs)) 124 elif self.op == '!=': 125 return self.left.GetOutcomes(env, defs).isdisjoint(self.right.GetOutcomes(env, defs)) 126 else: 127 assert self.op == '&&' 128 return self.left.Evaluate(env, defs) and self.right.Evaluate(env, defs) 129 130 def GetOutcomes(self, env, defs): 131 if self.op == '||' or self.op == ',': 132 return self.left.GetOutcomes(env, defs) | self.right.GetOutcomes(env, defs) 133 elif self.op == 'if': 134 if self.right.Evaluate(env, defs): return self.left.GetOutcomes(env, defs) 135 else: return set([]) 136 else: 137 assert self.op == '&&' 138 return self.left.GetOutcomes(env, defs) & self.right.GetOutcomes(env, defs) 139 140 def __str__(self): 141 return self.string(False) 142 143 def string(self, logical=False): 144 if self.op == 'if': 145 return "['%s', %s]" % (self.right.string(True), self.left.string(logical)) 146 elif self.op == "||" or self.op == ",": 147 if logical: 148 return "%s or %s" % (self.left.string(True), self.right.string(True)) 149 else: 150 return "%s, %s" % (self.left, self.right) 151 elif self.op == "&&": 152 return "%s and %s" % (self.left.string(True), self.right.string(True)) 153 return "%s %s %s" % (self.left.string(logical), self.op, 154 self.right.string(logical)) 155 156 157 def IsAlpha(string): 158 for char in string: 159 if not (char.isalpha() or char.isdigit() or char == '_'): 160 return False 161 return True 162 163 164 class Tokenizer(object): 165 """A simple string tokenizer that chops expressions into variables, 166 parens and operators""" 167 168 def __init__(self, expr): 169 self.index = 0 170 self.expr = expr 171 self.length = len(expr) 172 self.tokens = None 173 174 def Current(self, length=1): 175 if not self.HasMore(length): return "" 176 return self.expr[self.index:self.index + length] 177 178 def HasMore(self, length=1): 179 return self.index < self.length + (length - 1) 180 181 def Advance(self, count=1): 182 self.index = self.index + count 183 184 def AddToken(self, token): 185 self.tokens.append(token) 186 187 def SkipSpaces(self): 188 while self.HasMore() and self.Current().isspace(): 189 self.Advance() 190 191 def Tokenize(self): 192 self.tokens = [ ] 193 while self.HasMore(): 194 self.SkipSpaces() 195 if not self.HasMore(): 196 return None 197 if self.Current() == '(': 198 self.AddToken('(') 199 self.Advance() 200 elif self.Current() == ')': 201 self.AddToken(')') 202 self.Advance() 203 elif self.Current() == '$': 204 self.AddToken('$') 205 self.Advance() 206 elif self.Current() == ',': 207 self.AddToken(',') 208 self.Advance() 209 elif IsAlpha(self.Current()): 210 buf = "" 211 while self.HasMore() and IsAlpha(self.Current()): 212 buf += self.Current() 213 self.Advance() 214 self.AddToken(buf) 215 elif self.Current(2) == '&&': 216 self.AddToken('&&') 217 self.Advance(2) 218 elif self.Current(2) == '||': 219 self.AddToken('||') 220 self.Advance(2) 221 elif self.Current(2) == '==': 222 self.AddToken('==') 223 self.Advance(2) 224 elif self.Current(2) == '!=': 225 self.AddToken('!=') 226 self.Advance(2) 227 else: 228 return None 229 return self.tokens 230 231 232 class Scanner(object): 233 """A simple scanner that can serve out tokens from a given list""" 234 235 def __init__(self, tokens): 236 self.tokens = tokens 237 self.length = len(tokens) 238 self.index = 0 239 240 def HasMore(self): 241 return self.index < self.length 242 243 def Current(self): 244 return self.tokens[self.index] 245 246 def Advance(self): 247 self.index = self.index + 1 248 249 250 def ParseAtomicExpression(scan): 251 if scan.Current() == "true": 252 scan.Advance() 253 return Constant(True) 254 elif scan.Current() == "false": 255 scan.Advance() 256 return Constant(False) 257 elif IsAlpha(scan.Current()): 258 name = scan.Current() 259 scan.Advance() 260 return Outcome(name) 261 elif scan.Current() == '$': 262 scan.Advance() 263 if not IsAlpha(scan.Current()): 264 return None 265 name = scan.Current() 266 scan.Advance() 267 return Variable(name.lower()) 268 elif scan.Current() == '(': 269 scan.Advance() 270 result = ParseLogicalExpression(scan) 271 if (not result) or (scan.Current() != ')'): 272 return None 273 scan.Advance() 274 return result 275 else: 276 return None 277 278 279 BINARIES = ['==', '!='] 280 def ParseOperatorExpression(scan): 281 left = ParseAtomicExpression(scan) 282 if not left: return None 283 while scan.HasMore() and (scan.Current() in BINARIES): 284 op = scan.Current() 285 scan.Advance() 286 right = ParseOperatorExpression(scan) 287 if not right: 288 return None 289 left = Operation(left, op, right) 290 return left 291 292 293 def ParseConditionalExpression(scan): 294 left = ParseOperatorExpression(scan) 295 if not left: return None 296 while scan.HasMore() and (scan.Current() == 'if'): 297 scan.Advance() 298 right = ParseOperatorExpression(scan) 299 if not right: 300 return None 301 left = Operation(left, 'if', right) 302 return left 303 304 305 LOGICALS = ["&&", "||", ","] 306 def ParseLogicalExpression(scan): 307 left = ParseConditionalExpression(scan) 308 if not left: return None 309 while scan.HasMore() and (scan.Current() in LOGICALS): 310 op = scan.Current() 311 scan.Advance() 312 right = ParseConditionalExpression(scan) 313 if not right: 314 return None 315 left = Operation(left, op, right) 316 return left 317 318 319 def ParseCondition(expr): 320 """Parses a logical expression into an Expression object""" 321 tokens = Tokenizer(expr).Tokenize() 322 if not tokens: 323 print "Malformed expression: '%s'" % expr 324 return None 325 scan = Scanner(tokens) 326 ast = ParseLogicalExpression(scan) 327 if not ast: 328 print "Malformed expression: '%s'" % expr 329 return None 330 if scan.HasMore(): 331 print "Malformed expression: '%s'" % expr 332 return None 333 return ast 334 335 336 class Section(object): 337 """A section of the configuration file. Sections are enabled or 338 disabled prior to running the tests, based on their conditions""" 339 340 def __init__(self, condition): 341 self.condition = condition 342 self.rules = [ ] 343 344 def AddRule(self, rule): 345 self.rules.append(rule) 346 347 348 class Rule(object): 349 """A single rule that specifies the expected outcome for a single 350 test.""" 351 352 def __init__(self, raw_path, path, value): 353 self.raw_path = raw_path 354 self.path = path 355 self.value = value 356 357 def GetOutcomes(self, env, defs): 358 return self.value.GetOutcomes(env, defs) 359 360 def Contains(self, path): 361 if len(self.path) > len(path): 362 return False 363 for i in xrange(len(self.path)): 364 if not self.path[i].match(path[i]): 365 return False 366 return True 367 368 369 HEADER_PATTERN = re.compile(r'\[([^]]+)\]') 370 RULE_PATTERN = re.compile(r'\s*([^: ]*)\s*:(.*)') 371 DEF_PATTERN = re.compile(r'^def\s*(\w+)\s*=(.*)$') 372 PREFIX_PATTERN = re.compile(r'^\s*prefix\s+([\w\_\.\-\/]+)$') 373 374 375 class ConvertNotation(object): 376 def __init__(self, path): 377 self.path = path 378 self.indent = "" 379 self.comment = [] 380 self.init = False 381 self.section = False 382 self.out = cStringIO.StringIO() 383 384 def OpenGlobal(self): 385 if self.init: return 386 self.WriteComment() 387 print >> self.out, "[" 388 self.init = True 389 390 def CloseGlobal(self): 391 if not self.init: self.OpenGlobal() 392 print >> self.out, "]" 393 self.init = False 394 395 def OpenSection(self, condition="ALWAYS"): 396 if self.section: return 397 self.OpenGlobal() 398 if type(condition) != str: 399 condition = "'%s'" % condition.string(True) 400 print >> self.out, "%s[%s, {" % (self.indent, condition) 401 self.indent += " " * 2 402 self.section = condition 403 404 def CloseSection(self): 405 if not self.section: return 406 self.indent = self.indent[:-2] 407 print >> self.out, "%s}], # %s" % (self.indent, self.section) 408 self.section = False 409 410 def WriteComment(self): 411 if not self.comment: return 412 for c in self.comment: 413 if len(c.strip()) == 0: 414 print >> self.out, "" 415 else: 416 print >> self.out, "%s%s" % (self.indent, c), 417 self.comment = [] 418 419 def GetOutput(self): 420 with open(self.path) as f: 421 for line in f: 422 if line[0] == '#': 423 self.comment += [line] 424 continue 425 if len(line.strip()) == 0: 426 self.comment += [line] 427 continue 428 header_match = HEADER_PATTERN.match(line) 429 if header_match: 430 condition = ParseCondition(header_match.group(1).strip()) 431 self.CloseSection() 432 self.WriteComment() 433 self.OpenSection(condition) 434 continue 435 rule_match = RULE_PATTERN.match(line) 436 if rule_match: 437 self.OpenSection() 438 self.WriteComment() 439 path = rule_match.group(1).strip() 440 value_str = rule_match.group(2).strip() 441 comment = "" 442 if '#' in value_str: 443 pos = value_str.find('#') 444 comment = " %s" % value_str[pos:].strip() 445 value_str = value_str[:pos].strip() 446 value = ParseCondition(value_str) 447 print >> self.out, ("%s'%s': [%s],%s" % 448 (self.indent, path, value, comment)) 449 continue 450 def_match = DEF_PATTERN.match(line) 451 if def_match: 452 # Custom definitions are deprecated. 453 continue 454 prefix_match = PREFIX_PATTERN.match(line) 455 if prefix_match: 456 continue 457 print "Malformed line: '%s'." % line 458 self.CloseSection() 459 self.CloseGlobal() 460 result = self.out.getvalue() 461 self.out.close() 462 return result 463