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 28import os 29import re 30 31from variants import ALL_VARIANTS 32from utils import Freeze 33 34# These outcomes can occur in a TestCase's outcomes list: 35SKIP = "SKIP" 36FAIL = "FAIL" 37PASS = "PASS" 38OKAY = "OKAY" 39TIMEOUT = "TIMEOUT" 40CRASH = "CRASH" 41SLOW = "SLOW" 42FAST_VARIANTS = "FAST_VARIANTS" 43NO_VARIANTS = "NO_VARIANTS" 44# These are just for the status files and are mapped below in DEFS: 45FAIL_OK = "FAIL_OK" 46PASS_OR_FAIL = "PASS_OR_FAIL" 47FAIL_SLOPPY = "FAIL_SLOPPY" 48 49ALWAYS = "ALWAYS" 50 51KEYWORDS = {} 52for key in [SKIP, FAIL, PASS, OKAY, TIMEOUT, CRASH, SLOW, FAIL_OK, 53 FAST_VARIANTS, NO_VARIANTS, PASS_OR_FAIL, FAIL_SLOPPY, ALWAYS]: 54 KEYWORDS[key] = key 55 56DEFS = {FAIL_OK: [FAIL, OKAY], 57 PASS_OR_FAIL: [PASS, FAIL]} 58 59# Support arches, modes to be written as keywords instead of strings. 60VARIABLES = {ALWAYS: True} 61for var in ["debug", "release", "big", "little", 62 "android_arm", "android_arm64", "android_ia32", "android_x87", 63 "android_x64", "arm", "arm64", "ia32", "mips", "mipsel", "mips64", 64 "mips64el", "x64", "x87", "ppc", "ppc64", "s390", "s390x", "macos", 65 "windows", "linux", "aix"]: 66 VARIABLES[var] = var 67 68# Allow using variants as keywords. 69for var in ALL_VARIANTS: 70 VARIABLES[var] = var 71 72 73def DoSkip(outcomes): 74 return SKIP in outcomes 75 76 77def IsSlow(outcomes): 78 return SLOW in outcomes 79 80 81def OnlyStandardVariant(outcomes): 82 return NO_VARIANTS in outcomes 83 84 85def OnlyFastVariants(outcomes): 86 return FAST_VARIANTS in outcomes 87 88 89def IsPassOrFail(outcomes): 90 return ((PASS in outcomes) and (FAIL in outcomes) and 91 (not CRASH in outcomes) and (not OKAY in outcomes)) 92 93 94def IsFailOk(outcomes): 95 return (FAIL in outcomes) and (OKAY in outcomes) 96 97 98def _AddOutcome(result, new): 99 global DEFS 100 if new in DEFS: 101 mapped = DEFS[new] 102 if type(mapped) == list: 103 for m in mapped: 104 _AddOutcome(result, m) 105 elif type(mapped) == str: 106 _AddOutcome(result, mapped) 107 else: 108 result.add(new) 109 110 111def _JoinsPassAndFail(outcomes1, outcomes2): 112 """Indicates if we join PASS and FAIL from two different outcome sets and 113 the first doesn't already contain both. 114 """ 115 return ( 116 PASS in outcomes1 and 117 not FAIL in outcomes1 and 118 FAIL in outcomes2 119 ) 120 121VARIANT_EXPRESSION = object() 122 123def _EvalExpression(exp, variables): 124 try: 125 return eval(exp, variables) 126 except NameError as e: 127 identifier = re.match("name '(.*)' is not defined", e.message).group(1) 128 assert identifier == "variant", "Unknown identifier: %s" % identifier 129 return VARIANT_EXPRESSION 130 131 132def _EvalVariantExpression(section, rules, wildcards, variant, variables): 133 variables_with_variant = {} 134 variables_with_variant.update(variables) 135 variables_with_variant["variant"] = variant 136 result = _EvalExpression(section[0], variables_with_variant) 137 assert result != VARIANT_EXPRESSION 138 if result is True: 139 _ReadSection( 140 section[1], 141 rules[variant], 142 wildcards[variant], 143 variables_with_variant, 144 ) 145 else: 146 assert result is False, "Make sure expressions evaluate to boolean values" 147 148 149def _ParseOutcomeList(rule, outcomes, target_dict, variables): 150 result = set([]) 151 if type(outcomes) == str: 152 outcomes = [outcomes] 153 for item in outcomes: 154 if type(item) == str: 155 _AddOutcome(result, item) 156 elif type(item) == list: 157 exp = _EvalExpression(item[0], variables) 158 assert exp != VARIANT_EXPRESSION, ( 159 "Nested variant expressions are not supported") 160 if exp is False: 161 continue 162 163 # Ensure nobody uses an identifier by mistake, like "default", 164 # which would evaluate to true here otherwise. 165 assert exp is True, "Make sure expressions evaluate to boolean values" 166 167 for outcome in item[1:]: 168 assert type(outcome) == str 169 _AddOutcome(result, outcome) 170 else: 171 assert False 172 if len(result) == 0: return 173 if rule in target_dict: 174 # A FAIL without PASS in one rule has always precedence over a single 175 # PASS (without FAIL) in another. Otherwise the default PASS expectation 176 # in a rule with a modifier (e.g. PASS, SLOW) would be joined to a FAIL 177 # from another rule (which intended to mark a test as FAIL and not as 178 # PASS and FAIL). 179 if _JoinsPassAndFail(target_dict[rule], result): 180 target_dict[rule] -= set([PASS]) 181 if _JoinsPassAndFail(result, target_dict[rule]): 182 result -= set([PASS]) 183 target_dict[rule] |= result 184 else: 185 target_dict[rule] = result 186 187 188def ReadContent(content): 189 global KEYWORDS 190 return eval(content, KEYWORDS) 191 192 193def ReadStatusFile(content, variables): 194 # Empty defaults for rules and wildcards. Variant-independent 195 # rules are mapped by "", others by the variant name. 196 rules = {variant: {} for variant in ALL_VARIANTS} 197 rules[""] = {} 198 wildcards = {variant: {} for variant in ALL_VARIANTS} 199 wildcards[""] = {} 200 201 variables.update(VARIABLES) 202 for section in ReadContent(content): 203 assert type(section) == list 204 assert len(section) == 2 205 exp = _EvalExpression(section[0], variables) 206 if exp is False: 207 # The expression is variant-independent and evaluates to False. 208 continue 209 elif exp == VARIANT_EXPRESSION: 210 # If the expression contains one or more "variant" keywords, we evaluate 211 # it for all possible variants and create rules for those that apply. 212 for variant in ALL_VARIANTS: 213 _EvalVariantExpression(section, rules, wildcards, variant, variables) 214 else: 215 # The expression is variant-independent and evaluates to True. 216 assert exp is True, "Make sure expressions evaluate to boolean values" 217 _ReadSection( 218 section[1], 219 rules[""], 220 wildcards[""], 221 variables, 222 ) 223 return Freeze(rules), Freeze(wildcards) 224 225 226def _ReadSection(section, rules, wildcards, variables): 227 assert type(section) == dict 228 for rule in section: 229 assert type(rule) == str 230 if rule[-1] == '*': 231 _ParseOutcomeList(rule, section[rule], wildcards, variables) 232 else: 233 _ParseOutcomeList(rule, section[rule], rules, variables) 234 235JS_TEST_PATHS = { 236 'debugger': [[]], 237 'inspector': [[]], 238 'intl': [[]], 239 'message': [[]], 240 'mjsunit': [[]], 241 'mozilla': [['data']], 242 'test262': [['data', 'test'], ['local-tests', 'test']], 243 'webkit': [[]], 244} 245 246def PresubmitCheck(path): 247 with open(path) as f: 248 contents = ReadContent(f.read()) 249 basename = os.path.basename(os.path.dirname(path)) 250 root_prefix = basename + "/" 251 status = {"success": True} 252 def _assert(check, message): # Like "assert", but doesn't throw. 253 if not check: 254 print("%s: Error: %s" % (path, message)) 255 status["success"] = False 256 try: 257 for section in contents: 258 _assert(type(section) == list, "Section must be a list") 259 _assert(len(section) == 2, "Section list must have exactly 2 entries") 260 section = section[1] 261 _assert(type(section) == dict, 262 "Second entry of section must be a dictionary") 263 for rule in section: 264 _assert(type(rule) == str, "Rule key must be a string") 265 _assert(not rule.startswith(root_prefix), 266 "Suite name prefix must not be used in rule keys") 267 _assert(not rule.endswith('.js'), 268 ".js extension must not be used in rule keys.") 269 if basename in JS_TEST_PATHS and '*' not in rule: 270 _assert(any(os.path.exists(os.path.join(os.path.dirname(path), 271 *(paths + [rule + ".js"]))) 272 for paths in JS_TEST_PATHS[basename]), 273 "missing file for %s test %s" % (basename, rule)) 274 return status["success"] 275 except Exception as e: 276 print e 277 return False 278