js/src/tests/lib/manifest.py

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 # Library for JSTest manifests.
michael@0 2 #
michael@0 3 # This includes classes for representing and parsing JS manifests.
michael@0 4
michael@0 5 import os, os.path, re, sys
michael@0 6 from subprocess import Popen, PIPE
michael@0 7
michael@0 8 from tests import TestCase
michael@0 9
michael@0 10
michael@0 11 def split_path_into_dirs(path):
michael@0 12 dirs = [path]
michael@0 13
michael@0 14 while True:
michael@0 15 path, tail = os.path.split(path)
michael@0 16 if not tail:
michael@0 17 break
michael@0 18 dirs.append(path)
michael@0 19 return dirs
michael@0 20
michael@0 21 class XULInfo:
michael@0 22 def __init__(self, abi, os, isdebug):
michael@0 23 self.abi = abi
michael@0 24 self.os = os
michael@0 25 self.isdebug = isdebug
michael@0 26 self.browserIsRemote = False
michael@0 27
michael@0 28 def as_js(self):
michael@0 29 """Return JS that when executed sets up variables so that JS expression
michael@0 30 predicates on XUL build info evaluate properly."""
michael@0 31
michael@0 32 return ('var xulRuntime = { OS: "%s", XPCOMABI: "%s", shell: true };' +
michael@0 33 'var isDebugBuild=%s; var Android=%s; var browserIsRemote=%s') % (
michael@0 34 self.os,
michael@0 35 self.abi,
michael@0 36 str(self.isdebug).lower(),
michael@0 37 str(self.os == "Android").lower(),
michael@0 38 str(self.browserIsRemote).lower())
michael@0 39
michael@0 40 @classmethod
michael@0 41 def create(cls, jsdir):
michael@0 42 """Create a XULInfo based on the current platform's characteristics."""
michael@0 43
michael@0 44 # Our strategy is to find the autoconf.mk generated for the build and
michael@0 45 # read the values from there.
michael@0 46
michael@0 47 # Find config/autoconf.mk.
michael@0 48 dirs = split_path_into_dirs(os.getcwd()) + split_path_into_dirs(jsdir)
michael@0 49
michael@0 50 path = None
michael@0 51 for dir in dirs:
michael@0 52 _path = os.path.join(dir, 'config/autoconf.mk')
michael@0 53 if os.path.isfile(_path):
michael@0 54 path = _path
michael@0 55 break
michael@0 56
michael@0 57 if path == None:
michael@0 58 print ("Can't find config/autoconf.mk on a directory containing the JS shell"
michael@0 59 " (searched from %s)") % jsdir
michael@0 60 sys.exit(1)
michael@0 61
michael@0 62 # Read the values.
michael@0 63 val_re = re.compile(r'(TARGET_XPCOM_ABI|OS_TARGET|MOZ_DEBUG)\s*=\s*(.*)')
michael@0 64 kw = { 'isdebug': False }
michael@0 65 for line in open(path):
michael@0 66 m = val_re.match(line)
michael@0 67 if m:
michael@0 68 key, val = m.groups()
michael@0 69 val = val.rstrip()
michael@0 70 if key == 'TARGET_XPCOM_ABI':
michael@0 71 kw['abi'] = val
michael@0 72 if key == 'OS_TARGET':
michael@0 73 kw['os'] = val
michael@0 74 if key == 'MOZ_DEBUG':
michael@0 75 kw['isdebug'] = (val == '1')
michael@0 76 return cls(**kw)
michael@0 77
michael@0 78 class XULInfoTester:
michael@0 79 def __init__(self, xulinfo, js_bin):
michael@0 80 self.js_prolog = xulinfo.as_js()
michael@0 81 self.js_bin = js_bin
michael@0 82 # Maps JS expr to evaluation result.
michael@0 83 self.cache = {}
michael@0 84
michael@0 85 def test(self, cond):
michael@0 86 """Test a XUL predicate condition against this local info."""
michael@0 87 ans = self.cache.get(cond, None)
michael@0 88 if ans is None:
michael@0 89 cmd = [ self.js_bin, '-e', self.js_prolog, '-e', 'print(!!(%s))'%cond ]
michael@0 90 p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
michael@0 91 out, err = p.communicate()
michael@0 92 if out in ('true\n', 'true\r\n'):
michael@0 93 ans = True
michael@0 94 elif out in ('false\n', 'false\r\n'):
michael@0 95 ans = False
michael@0 96 else:
michael@0 97 raise Exception(("Failed to test XUL condition %r;"
michael@0 98 + " output was %r, stderr was %r")
michael@0 99 % (cond, out, err))
michael@0 100 self.cache[cond] = ans
michael@0 101 return ans
michael@0 102
michael@0 103 class NullXULInfoTester:
michael@0 104 """Can be used to parse manifests without a JS shell."""
michael@0 105 def test(self, cond):
michael@0 106 return False
michael@0 107
michael@0 108 def _parse_one(testcase, xul_tester):
michael@0 109 pos = 0
michael@0 110 parts = testcase.terms.split()
michael@0 111 while pos < len(parts):
michael@0 112 if parts[pos] == 'fails':
michael@0 113 testcase.expect = False
michael@0 114 pos += 1
michael@0 115 elif parts[pos] == 'skip':
michael@0 116 testcase.expect = testcase.enable = False
michael@0 117 pos += 1
michael@0 118 elif parts[pos] == 'random':
michael@0 119 testcase.random = True
michael@0 120 pos += 1
michael@0 121 elif parts[pos].startswith('fails-if'):
michael@0 122 cond = parts[pos][len('fails-if('):-1]
michael@0 123 if xul_tester.test(cond):
michael@0 124 testcase.expect = False
michael@0 125 pos += 1
michael@0 126 elif parts[pos].startswith('asserts-if'):
michael@0 127 # This directive means we may flunk some number of
michael@0 128 # NS_ASSERTIONs in the browser. For the shell, ignore it.
michael@0 129 pos += 1
michael@0 130 elif parts[pos].startswith('skip-if'):
michael@0 131 cond = parts[pos][len('skip-if('):-1]
michael@0 132 if xul_tester.test(cond):
michael@0 133 testcase.expect = testcase.enable = False
michael@0 134 pos += 1
michael@0 135 elif parts[pos].startswith('random-if'):
michael@0 136 cond = parts[pos][len('random-if('):-1]
michael@0 137 if xul_tester.test(cond):
michael@0 138 testcase.random = True
michael@0 139 pos += 1
michael@0 140 elif parts[pos].startswith('require-or'):
michael@0 141 cond = parts[pos][len('require-or('):-1]
michael@0 142 (preconditions, fallback_action) = re.split(",", cond)
michael@0 143 for precondition in re.split("&&", preconditions):
michael@0 144 if precondition == 'debugMode':
michael@0 145 testcase.options.append('-d')
michael@0 146 elif precondition == 'true':
michael@0 147 pass
michael@0 148 else:
michael@0 149 if fallback_action == "skip":
michael@0 150 testcase.expect = testcase.enable = False
michael@0 151 elif fallback_action == "fail":
michael@0 152 testcase.expect = False
michael@0 153 elif fallback_action == "random":
michael@0 154 testcase.random = True
michael@0 155 else:
michael@0 156 raise Exception(("Invalid precondition '%s' or fallback " +
michael@0 157 " action '%s'") % (precondition, fallback_action))
michael@0 158 break
michael@0 159 pos += 1
michael@0 160 elif parts[pos] == 'slow':
michael@0 161 testcase.slow = True
michael@0 162 pos += 1
michael@0 163 elif parts[pos] == 'silentfail':
michael@0 164 # silentfails use tons of memory, and Darwin doesn't support ulimit.
michael@0 165 if xul_tester.test("xulRuntime.OS == 'Darwin'"):
michael@0 166 testcase.expect = testcase.enable = False
michael@0 167 pos += 1
michael@0 168 else:
michael@0 169 print 'warning: invalid manifest line element "%s"'%parts[pos]
michael@0 170 pos += 1
michael@0 171
michael@0 172 def _build_manifest_script_entry(script_name, test):
michael@0 173 line = []
michael@0 174 if test.terms:
michael@0 175 line.append(test.terms)
michael@0 176 line.append("script")
michael@0 177 line.append(script_name)
michael@0 178 if test.comment:
michael@0 179 line.append("#")
michael@0 180 line.append(test.comment)
michael@0 181 return ' '.join(line)
michael@0 182
michael@0 183 def _map_prefixes_left(test_list):
michael@0 184 """
michael@0 185 Splits tests into a dictionary keyed on the first component of the test
michael@0 186 path, aggregating tests with a common base path into a list.
michael@0 187 """
michael@0 188 byprefix = {}
michael@0 189 for t in test_list:
michael@0 190 left, sep, remainder = t.path.partition(os.sep)
michael@0 191 if left not in byprefix:
michael@0 192 byprefix[left] = []
michael@0 193 if remainder:
michael@0 194 t.path = remainder
michael@0 195 byprefix[left].append(t)
michael@0 196 return byprefix
michael@0 197
michael@0 198 def _emit_manifest_at(location, relative, test_list, depth):
michael@0 199 """
michael@0 200 location - str: absolute path where we want to write the manifest
michael@0 201 relative - str: relative path from topmost manifest directory to current
michael@0 202 test_list - [str]: list of all test paths and directorys
michael@0 203 depth - int: number of dirs we are below the topmost manifest dir
michael@0 204 """
michael@0 205 manifests = _map_prefixes_left(test_list)
michael@0 206
michael@0 207 filename = os.path.join(location, 'jstests.list')
michael@0 208 manifest = []
michael@0 209 numTestFiles = 0
michael@0 210 for k, test_list in manifests.iteritems():
michael@0 211 fullpath = os.path.join(location, k)
michael@0 212 if os.path.isdir(fullpath):
michael@0 213 manifest.append("include " + k + "/jstests.list")
michael@0 214 relpath = os.path.join(relative, k)
michael@0 215 _emit_manifest_at(fullpath, relpath, test_list, depth + 1)
michael@0 216 else:
michael@0 217 numTestFiles += 1
michael@0 218 assert len(test_list) == 1
michael@0 219 line = _build_manifest_script_entry(k, test_list[0])
michael@0 220 manifest.append(line)
michael@0 221
michael@0 222 # Always present our manifest in sorted order.
michael@0 223 manifest.sort()
michael@0 224
michael@0 225 # If we have tests, we have to set the url-prefix so reftest can find them.
michael@0 226 if numTestFiles > 0:
michael@0 227 manifest = (["url-prefix %sjsreftest.html?test=%s/" % ('../' * depth, relative)]
michael@0 228 + manifest)
michael@0 229
michael@0 230 fp = open(filename, 'w')
michael@0 231 try:
michael@0 232 fp.write('\n'.join(manifest) + '\n')
michael@0 233 finally:
michael@0 234 fp.close()
michael@0 235
michael@0 236 def make_manifests(location, test_list):
michael@0 237 _emit_manifest_at(location, '', test_list, 0)
michael@0 238
michael@0 239 def _find_all_js_files(base, location):
michael@0 240 for root, dirs, files in os.walk(location):
michael@0 241 root = root[len(base) + 1:]
michael@0 242 for fn in files:
michael@0 243 if fn.endswith('.js'):
michael@0 244 yield root, fn
michael@0 245
michael@0 246 TEST_HEADER_PATTERN_INLINE = re.compile(r'//\s*\|(.*?)\|\s*(.*?)\s*(--\s*(.*))?$')
michael@0 247 TEST_HEADER_PATTERN_MULTI = re.compile(r'/\*\s*\|(.*?)\|\s*(.*?)\s*(--\s*(.*))?\*/')
michael@0 248
michael@0 249 def _parse_test_header(fullpath, testcase, xul_tester):
michael@0 250 """
michael@0 251 This looks a bit weird. The reason is that it needs to be efficient, since
michael@0 252 it has to be done on every test
michael@0 253 """
michael@0 254 fp = open(fullpath, 'r')
michael@0 255 try:
michael@0 256 buf = fp.read(512)
michael@0 257 finally:
michael@0 258 fp.close()
michael@0 259
michael@0 260 # Bail early if we do not start with a single comment.
michael@0 261 if not buf.startswith("//"):
michael@0 262 return
michael@0 263
michael@0 264 # Extract the token.
michael@0 265 buf, _, _ = buf.partition('\n')
michael@0 266 matches = TEST_HEADER_PATTERN_INLINE.match(buf)
michael@0 267
michael@0 268 if not matches:
michael@0 269 matches = TEST_HEADER_PATTERN_MULTI.match(buf)
michael@0 270 if not matches:
michael@0 271 return
michael@0 272
michael@0 273 testcase.tag = matches.group(1)
michael@0 274 testcase.terms = matches.group(2)
michael@0 275 testcase.comment = matches.group(4)
michael@0 276
michael@0 277 _parse_one(testcase, xul_tester)
michael@0 278
michael@0 279 def _parse_external_manifest(filename, relpath):
michael@0 280 """
michael@0 281 Reads an external manifest file for test suites whose individual test cases
michael@0 282 can't be decorated with reftest comments.
michael@0 283 filename - str: name of the manifest file
michael@0 284 relpath - str: relative path of the directory containing the manifest
michael@0 285 within the test suite
michael@0 286 """
michael@0 287 entries = []
michael@0 288
michael@0 289 with open(filename, 'r') as fp:
michael@0 290 manifest_re = re.compile(r'^\s*(.*)\s+(include|script)\s+(\S+)$')
michael@0 291 for line in fp:
michael@0 292 line, _, comment = line.partition('#')
michael@0 293 line = line.strip()
michael@0 294 if not line:
michael@0 295 continue
michael@0 296 matches = manifest_re.match(line)
michael@0 297 if not matches:
michael@0 298 print('warning: unrecognized line in jstests.list: {0}'.format(line))
michael@0 299 continue
michael@0 300
michael@0 301 path = os.path.normpath(os.path.join(relpath, matches.group(3)))
michael@0 302 if matches.group(2) == 'include':
michael@0 303 # The manifest spec wants a reference to another manifest here,
michael@0 304 # but we need just the directory. We do need the trailing
michael@0 305 # separator so we don't accidentally match other paths of which
michael@0 306 # this one is a prefix.
michael@0 307 assert(path.endswith('jstests.list'))
michael@0 308 path = path[:-len('jstests.list')]
michael@0 309
michael@0 310 entries.append({'path': path, 'terms': matches.group(1), 'comment': comment.strip()})
michael@0 311
michael@0 312 # if one directory name is a prefix of another, we want the shorter one first
michael@0 313 entries.sort(key=lambda x: x["path"])
michael@0 314 return entries
michael@0 315
michael@0 316 def _apply_external_manifests(filename, testcase, entries, xul_tester):
michael@0 317 for entry in entries:
michael@0 318 if filename.startswith(entry["path"]):
michael@0 319 # The reftest spec would require combining the terms (failure types)
michael@0 320 # that may already be defined in the test case with the terms
michael@0 321 # specified in entry; for example, a skip overrides a random, which
michael@0 322 # overrides a fails. Since we don't necessarily know yet in which
michael@0 323 # environment the test cases will be run, we'd also have to
michael@0 324 # consider skip-if, random-if, and fails-if with as-yet unresolved
michael@0 325 # conditions.
michael@0 326 # At this point, we use external manifests only for test cases
michael@0 327 # that can't have their own failure type comments, so we simply
michael@0 328 # use the terms for the most specific path.
michael@0 329 testcase.terms = entry["terms"]
michael@0 330 testcase.comment = entry["comment"]
michael@0 331 _parse_one(testcase, xul_tester)
michael@0 332
michael@0 333 def load(location, xul_tester, reldir = ''):
michael@0 334 """
michael@0 335 Locates all tests by walking the filesystem starting at |location|.
michael@0 336 Uses xul_tester to evaluate any test conditions in the test header.
michael@0 337 Failure type and comment for a test case can come from
michael@0 338 - an external manifest entry for the test case,
michael@0 339 - an external manifest entry for a containing directory,
michael@0 340 - most commonly: the header of the test case itself.
michael@0 341 """
michael@0 342 # The list of tests that we are collecting.
michael@0 343 tests = []
michael@0 344
michael@0 345 # Any file whose basename matches something in this set is ignored.
michael@0 346 EXCLUDED = set(('browser.js', 'shell.js', 'jsref.js', 'template.js',
michael@0 347 'user.js', 'sta.js',
michael@0 348 'test262-browser.js', 'test262-shell.js',
michael@0 349 'test402-browser.js', 'test402-shell.js',
michael@0 350 'testBuiltInObject.js', 'testIntl.js',
michael@0 351 'js-test-driver-begin.js', 'js-test-driver-end.js'))
michael@0 352
michael@0 353 manifestFile = os.path.join(location, 'jstests.list')
michael@0 354 externalManifestEntries = _parse_external_manifest(manifestFile, '')
michael@0 355
michael@0 356 for root, basename in _find_all_js_files(location, location):
michael@0 357 # Skip js files in the root test directory.
michael@0 358 if not root:
michael@0 359 continue
michael@0 360
michael@0 361 # Skip files that we know are not tests.
michael@0 362 if basename in EXCLUDED:
michael@0 363 continue
michael@0 364
michael@0 365 # Get the full path and relative location of the file.
michael@0 366 filename = os.path.join(root, basename)
michael@0 367 fullpath = os.path.join(location, filename)
michael@0 368
michael@0 369 # Skip empty files.
michael@0 370 statbuf = os.stat(fullpath)
michael@0 371 if statbuf.st_size == 0:
michael@0 372 continue
michael@0 373
michael@0 374 testcase = TestCase(os.path.join(reldir, filename))
michael@0 375 _apply_external_manifests(filename, testcase, externalManifestEntries, xul_tester)
michael@0 376 _parse_test_header(fullpath, testcase, xul_tester)
michael@0 377 tests.append(testcase)
michael@0 378 return tests

mercurial