Home | History | Annotate | Download | only in local
      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