PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/js/src/tests/manifest.py

https://bitbucket.org/bgirard/mozilla-central
Python | 326 lines | 295 code | 19 blank | 12 comment | 10 complexity | ac9e0b9f8ef8f3d9589cc14053054c3e MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause, GPL-2.0, Apache-2.0, MIT, JSON, 0BSD, BSD-2-Clause, LGPL-3.0, AGPL-1.0
  1. # Library for JSTest manifests.
  2. #
  3. # This includes classes for representing and parsing JS manifests.
  4. import os, os.path, re, sys
  5. from subprocess import *
  6. from tests import TestCase
  7. def split_path_into_dirs(path):
  8. dirs = [path]
  9. while True:
  10. path, tail = os.path.split(path)
  11. if not tail:
  12. break
  13. dirs.append(path)
  14. return dirs
  15. class XULInfo:
  16. def __init__(self, abi, os, isdebug):
  17. self.abi = abi
  18. self.os = os
  19. self.isdebug = isdebug
  20. self.browserIsRemote = False
  21. def as_js(self):
  22. """Return JS that when executed sets up variables so that JS expression
  23. predicates on XUL build info evaluate properly."""
  24. return ('var xulRuntime = { OS: "%s", XPCOMABI: "%s", shell: true };' +
  25. 'var isDebugBuild=%s; var Android=%s; var browserIsRemote=%s') % (
  26. self.os,
  27. self.abi,
  28. str(self.isdebug).lower(),
  29. str(self.os == "Android").lower(),
  30. str(self.browserIsRemote).lower())
  31. @classmethod
  32. def create(cls, jsdir):
  33. """Create a XULInfo based on the current platform's characteristics."""
  34. # Our strategy is to find the autoconf.mk generated for the build and
  35. # read the values from there.
  36. # Find config/autoconf.mk.
  37. dirs = split_path_into_dirs(os.getcwd()) + split_path_into_dirs(jsdir)
  38. path = None
  39. for dir in dirs:
  40. _path = os.path.join(dir, 'config/autoconf.mk')
  41. if os.path.isfile(_path):
  42. path = _path
  43. break
  44. if path == None:
  45. print ("Can't find config/autoconf.mk on a directory containing the JS shell"
  46. " (searched from %s)") % jsdir
  47. sys.exit(1)
  48. # Read the values.
  49. val_re = re.compile(r'(TARGET_XPCOM_ABI|OS_TARGET|MOZ_DEBUG)\s*=\s*(.*)')
  50. kw = {}
  51. for line in open(path):
  52. m = val_re.match(line)
  53. if m:
  54. key, val = m.groups()
  55. val = val.rstrip()
  56. if key == 'TARGET_XPCOM_ABI':
  57. kw['abi'] = val
  58. if key == 'OS_TARGET':
  59. kw['os'] = val
  60. if key == 'MOZ_DEBUG':
  61. kw['isdebug'] = (val == '1')
  62. return cls(**kw)
  63. class XULInfoTester:
  64. def __init__(self, xulinfo, js_bin):
  65. self.js_prolog = xulinfo.as_js()
  66. self.js_bin = js_bin
  67. # Maps JS expr to evaluation result.
  68. self.cache = {}
  69. def test(self, cond):
  70. """Test a XUL predicate condition against this local info."""
  71. ans = self.cache.get(cond, None)
  72. if ans is None:
  73. cmd = [ self.js_bin, '-e', self.js_prolog, '-e', 'print(!!(%s))'%cond ]
  74. p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  75. out, err = p.communicate()
  76. if out in ('true\n', 'true\r\n'):
  77. ans = True
  78. elif out in ('false\n', 'false\r\n'):
  79. ans = False
  80. else:
  81. raise Exception(("Failed to test XUL condition %r;"
  82. + " output was %r, stderr was %r")
  83. % (cond, out, err))
  84. self.cache[cond] = ans
  85. return ans
  86. class NullXULInfoTester:
  87. """Can be used to parse manifests without a JS shell."""
  88. def test(self, cond):
  89. return False
  90. def _parse_one(parts, xul_tester):
  91. script = None
  92. enable = True
  93. expect = True
  94. random = False
  95. slow = False
  96. debugMode = False
  97. pos = 0
  98. while pos < len(parts):
  99. if parts[pos] == 'fails':
  100. expect = False
  101. pos += 1
  102. elif parts[pos] == 'skip':
  103. expect = enable = False
  104. pos += 1
  105. elif parts[pos] == 'random':
  106. random = True
  107. pos += 1
  108. elif parts[pos].startswith('fails-if'):
  109. cond = parts[pos][len('fails-if('):-1]
  110. if xul_tester.test(cond):
  111. expect = False
  112. pos += 1
  113. elif parts[pos].startswith('asserts-if'):
  114. # This directive means we may flunk some number of
  115. # NS_ASSERTIONs in the browser. For the shell, ignore it.
  116. pos += 1
  117. elif parts[pos].startswith('skip-if'):
  118. cond = parts[pos][len('skip-if('):-1]
  119. if xul_tester.test(cond):
  120. expect = enable = False
  121. pos += 1
  122. elif parts[pos].startswith('random-if'):
  123. cond = parts[pos][len('random-if('):-1]
  124. if xul_tester.test(cond):
  125. random = True
  126. pos += 1
  127. elif parts[pos].startswith('require-or'):
  128. cond = parts[pos][len('require-or('):-1]
  129. (preconditions, fallback_action) = re.split(",", cond)
  130. for precondition in re.split("&&", preconditions):
  131. if precondition == 'debugMode':
  132. debugMode = True
  133. elif precondition == 'true':
  134. pass
  135. else:
  136. if fallback_action == "skip":
  137. expect = enable = False
  138. elif fallback_action == "fail":
  139. expect = False
  140. elif fallback_action == "random":
  141. random = True
  142. else:
  143. raise Exception(("Invalid precondition '%s' or fallback " +
  144. " action '%s'") % (precondition, fallback_action))
  145. break
  146. pos += 1
  147. elif parts[pos] == 'script':
  148. script = parts[pos+1]
  149. pos += 2
  150. elif parts[pos] == 'slow':
  151. slow = True
  152. pos += 1
  153. elif parts[pos] == 'silentfail':
  154. # silentfails use tons of memory, and Darwin doesn't support ulimit.
  155. if xul_tester.test("xulRuntime.OS == 'Darwin'"):
  156. expect = enable = False
  157. pos += 1
  158. else:
  159. print 'warning: invalid manifest line element "%s"'%parts[pos]
  160. pos += 1
  161. return script, (enable, expect, random, slow, debugMode)
  162. def _map_prefixes_left(test_list):
  163. """
  164. Splits tests into a dictionary keyed on the first component of the test
  165. path, aggregating tests with a common base path into a list.
  166. """
  167. byprefix = {}
  168. for t in test_list:
  169. left, sep, remainder = t.path.partition(os.sep)
  170. if left not in byprefix:
  171. byprefix[left] = []
  172. if remainder:
  173. t.path = remainder
  174. byprefix[left].append(t)
  175. return byprefix
  176. def _emit_manifest_at(location, relative, test_list, depth):
  177. manifests = _map_prefixes_left(test_list)
  178. filename = os.path.join(location, 'jstests.list')
  179. manifest = []
  180. numTestFiles = 0
  181. for k, test_list in manifests.iteritems():
  182. fullpath = os.path.join(location, k)
  183. if os.path.isdir(fullpath):
  184. manifest.append("include " + k + "/jstests.list")
  185. relpath = os.path.join(relative, k)
  186. _emit_manifest_at(fullpath, relpath, test_list, depth + 1)
  187. else:
  188. numTestFiles += 1
  189. assert(len(test_list) == 1)
  190. t = test_list[0]
  191. line = []
  192. if t.terms:
  193. line.append(t.terms)
  194. line.append("script")
  195. line.append(k)
  196. if t.comment:
  197. line.append("#")
  198. line.append(t.comment)
  199. manifest.append(' '.join(line))
  200. # Always present our manifest in sorted order.
  201. manifest.sort()
  202. # If we have tests, we have to set the url-prefix so reftest can find them.
  203. if numTestFiles > 0:
  204. manifest = (["url-prefix %sjsreftest.html?test=%s/" % ('../' * depth, relative)]
  205. + manifest)
  206. fp = open(filename, 'w')
  207. try:
  208. fp.write('\n'.join(manifest) + '\n')
  209. finally:
  210. fp.close()
  211. def make_manifests(location, test_list):
  212. _emit_manifest_at(location, '', test_list, 0)
  213. def _find_all_js_files(base, location):
  214. for root, dirs, files in os.walk(location):
  215. root = root[len(base) + 1:]
  216. for fn in files:
  217. if fn.endswith('.js'):
  218. yield root, fn
  219. TEST_HEADER_PATTERN_INLINE = re.compile(r'//\s*\|(.*?)\|\s*(.*?)\s*(--\s*(.*))?$')
  220. TEST_HEADER_PATTERN_MULTI = re.compile(r'/\*\s*\|(.*?)\|\s*(.*?)\s*(--\s*(.*))?\*/')
  221. def _parse_test_header(fullpath, testcase, xul_tester):
  222. """
  223. This looks a bit weird. The reason is that it needs to be efficient, since
  224. it has to be done on every test
  225. """
  226. fp = open(fullpath, 'r')
  227. try:
  228. buf = fp.read(512)
  229. finally:
  230. fp.close()
  231. # Bail early if we do not start with a single comment.
  232. if not buf.startswith("//"):
  233. return
  234. # Extract the token.
  235. buf, _, _ = buf.partition('\n')
  236. matches = TEST_HEADER_PATTERN_INLINE.match(buf)
  237. if not matches:
  238. matches = TEST_HEADER_PATTERN_MULTI.match(buf)
  239. if not matches:
  240. return
  241. testcase.tag = matches.group(1)
  242. testcase.terms = matches.group(2)
  243. testcase.comment = matches.group(4)
  244. _, properties = _parse_one(testcase.terms.split(), xul_tester)
  245. testcase.enable = properties[0]
  246. testcase.expect = properties[1]
  247. testcase.random = properties[2]
  248. testcase.slow = properties[3]
  249. testcase.debugMode = properties[4]
  250. def load(location, xul_tester, reldir = ''):
  251. """
  252. Locates all tests by walking the filesystem starting at |location|.
  253. Uses xul_tester to evaluate any test conditions in the test header.
  254. """
  255. # The list of tests that we are collecting.
  256. tests = []
  257. # Any file who's basename matches something in this set is ignored.
  258. EXCLUDED = set(('browser.js', 'shell.js', 'jsref.js', 'template.js',
  259. 'user.js', 'js-test-driver-begin.js', 'js-test-driver-end.js'))
  260. for root, basename in _find_all_js_files(location, location):
  261. # Skip js files in the root test directory.
  262. if not root:
  263. continue
  264. # Skip files that we know are not tests.
  265. if basename in EXCLUDED:
  266. continue
  267. # Get the full path and relative location of the file.
  268. filename = os.path.join(root, basename)
  269. fullpath = os.path.join(location, filename)
  270. # Skip empty files.
  271. statbuf = os.stat(fullpath)
  272. if statbuf.st_size == 0:
  273. continue
  274. # Parse the test header and load the test.
  275. testcase = TestCase(os.path.join(reldir, filename),
  276. enable = True,
  277. expect = True,
  278. random = False,
  279. slow = False,
  280. debugMode = False)
  281. _parse_test_header(fullpath, testcase, xul_tester)
  282. tests.append(testcase)
  283. return tests