js/src/gdb/run-tests.py

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 #!/usr/bin/env python
michael@0 2 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 5
michael@0 6 # run-tests.py -- Python harness for GDB SpiderMonkey support
michael@0 7
michael@0 8 import os, re, subprocess, sys, traceback
michael@0 9 from threading import Thread
michael@0 10
michael@0 11 # From this directory:
michael@0 12 import progressbar
michael@0 13 from taskpool import TaskPool, get_cpu_count
michael@0 14
michael@0 15 # Backported from Python 3.1 posixpath.py
michael@0 16 def _relpath(path, start=None):
michael@0 17 """Return a relative version of a path"""
michael@0 18
michael@0 19 if not path:
michael@0 20 raise ValueError("no path specified")
michael@0 21
michael@0 22 if start is None:
michael@0 23 start = os.curdir
michael@0 24
michael@0 25 start_list = os.path.abspath(start).split(os.sep)
michael@0 26 path_list = os.path.abspath(path).split(os.sep)
michael@0 27
michael@0 28 # Work out how much of the filepath is shared by start and path.
michael@0 29 i = len(os.path.commonprefix([start_list, path_list]))
michael@0 30
michael@0 31 rel_list = [os.pardir] * (len(start_list)-i) + path_list[i:]
michael@0 32 if not rel_list:
michael@0 33 return os.curdir
michael@0 34 return os.path.join(*rel_list)
michael@0 35
michael@0 36 os.path.relpath = _relpath
michael@0 37
michael@0 38 # Characters that need to be escaped when used in shell words.
michael@0 39 shell_need_escapes = re.compile('[^\w\d%+,-./:=@\'"]', re.DOTALL)
michael@0 40 # Characters that need to be escaped within double-quoted strings.
michael@0 41 shell_dquote_escapes = re.compile('[^\w\d%+,-./:=@"]', re.DOTALL)
michael@0 42 def make_shell_cmd(l):
michael@0 43 def quote(s):
michael@0 44 if shell_need_escapes.search(s):
michael@0 45 if s.find("'") < 0:
michael@0 46 return "'" + s + "'"
michael@0 47 return '"' + shell_dquote_escapes.sub('\\g<0>', s) + '"'
michael@0 48 return s
michael@0 49
michael@0 50 return ' '.join([quote(_) for _ in l])
michael@0 51
michael@0 52 # An instance of this class collects the lists of passing, failing, and
michael@0 53 # timing-out tests, runs the progress bar, and prints a summary at the end.
michael@0 54 class Summary(object):
michael@0 55
michael@0 56 class SummaryBar(progressbar.ProgressBar):
michael@0 57 def __init__(self, limit):
michael@0 58 super(Summary.SummaryBar, self).__init__('', limit, 24)
michael@0 59 def start(self):
michael@0 60 self.label = '[starting ]'
michael@0 61 self.update(0)
michael@0 62 def counts(self, run, failures, timeouts):
michael@0 63 self.label = '[%4d|%4d|%4d|%4d]' % (run - failures, failures, timeouts, run)
michael@0 64 self.update(run)
michael@0 65
michael@0 66 def __init__(self, num_tests):
michael@0 67 self.run = 0
michael@0 68 self.failures = [] # kind of judgemental; "unexpecteds"?
michael@0 69 self.timeouts = []
michael@0 70 if not OPTIONS.hide_progress:
michael@0 71 self.bar = Summary.SummaryBar(num_tests)
michael@0 72
michael@0 73 # Progress bar control.
michael@0 74 def start(self):
michael@0 75 if not OPTIONS.hide_progress:
michael@0 76 self.bar.start()
michael@0 77 def update(self):
michael@0 78 if not OPTIONS.hide_progress:
michael@0 79 self.bar.counts(self.run, len(self.failures), len(self.timeouts))
michael@0 80 # Call 'thunk' to show some output, while getting the progress bar out of the way.
michael@0 81 def interleave_output(self, thunk):
michael@0 82 if not OPTIONS.hide_progress:
michael@0 83 self.bar.clear()
michael@0 84 thunk()
michael@0 85 self.update()
michael@0 86
michael@0 87 def passed(self, test):
michael@0 88 self.run += 1
michael@0 89 self.update()
michael@0 90
michael@0 91 def failed(self, test):
michael@0 92 self.run += 1
michael@0 93 self.failures.append(test)
michael@0 94 self.update()
michael@0 95
michael@0 96 def timeout(self, test):
michael@0 97 self.run += 1
michael@0 98 self.timeouts.append(test)
michael@0 99 self.update()
michael@0 100
michael@0 101 def finish(self):
michael@0 102 if not OPTIONS.hide_progress:
michael@0 103 self.bar.finish()
michael@0 104
michael@0 105 if self.failures:
michael@0 106
michael@0 107 print "tests failed:"
michael@0 108 for test in self.failures:
michael@0 109 test.show(sys.stdout)
michael@0 110
michael@0 111 if OPTIONS.worklist:
michael@0 112 try:
michael@0 113 with open(OPTIONS.worklist) as out:
michael@0 114 for test in self.failures:
michael@0 115 out.write(test.name + '\n')
michael@0 116 except IOError as err:
michael@0 117 sys.stderr.write("Error writing worklist file '%s': %s"
michael@0 118 % (OPTIONS.worklist, err))
michael@0 119 sys.exit(1)
michael@0 120
michael@0 121 if OPTIONS.write_failures:
michael@0 122 try:
michael@0 123 with open(OPTIONS.write_failures) as out:
michael@0 124 for test in self.failures:
michael@0 125 test.show(out)
michael@0 126 except IOError as err:
michael@0 127 sys.stderr.write("Error writing worklist file '%s': %s"
michael@0 128 % (OPTIONS.write_failures, err))
michael@0 129 sys.exit(1)
michael@0 130
michael@0 131 if self.timeouts:
michael@0 132 print "tests timed out:"
michael@0 133 for test in self.timeouts:
michael@0 134 test.show(sys.stdout)
michael@0 135
michael@0 136 if self.failures or self.timeouts:
michael@0 137 sys.exit(2)
michael@0 138
michael@0 139 class Test(TaskPool.Task):
michael@0 140 def __init__(self, path, summary):
michael@0 141 super(Test, self).__init__()
michael@0 142 self.test_path = path # path to .py test file
michael@0 143 self.summary = summary
michael@0 144
michael@0 145 # test.name is the name of the test relative to the top of the test
michael@0 146 # directory. This is what we use to report failures and timeouts,
michael@0 147 # and when writing test lists.
michael@0 148 self.name = os.path.relpath(self.test_path, OPTIONS.testdir)
michael@0 149
michael@0 150 self.stdout = ''
michael@0 151 self.stderr = ''
michael@0 152 self.returncode = None
michael@0 153
michael@0 154 def cmd(self):
michael@0 155 testlibdir = os.path.normpath(os.path.join(OPTIONS.testdir, '..', 'lib-for-tests'))
michael@0 156 return [OPTIONS.gdb_executable,
michael@0 157 '-nw', # Don't create a window (unnecessary?)
michael@0 158 '-nx', # Don't read .gdbinit.
michael@0 159 '--ex', 'add-auto-load-safe-path %s' % (OPTIONS.builddir,),
michael@0 160 '--ex', 'set env LD_LIBRARY_PATH %s' % (OPTIONS.libdir,),
michael@0 161 '--ex', 'file %s' % (os.path.join(OPTIONS.builddir, 'gdb-tests'),),
michael@0 162 '--eval-command', 'python testlibdir=%r' % (testlibdir,),
michael@0 163 '--eval-command', 'python testscript=%r' % (self.test_path,),
michael@0 164 '--eval-command', 'python execfile(%r)' % os.path.join(testlibdir, 'catcher.py')]
michael@0 165
michael@0 166 def start(self, pipe, deadline):
michael@0 167 super(Test, self).start(pipe, deadline)
michael@0 168 if OPTIONS.show_cmd:
michael@0 169 self.summary.interleave_output(lambda: self.show_cmd(sys.stdout))
michael@0 170
michael@0 171 def onStdout(self, text):
michael@0 172 self.stdout += text
michael@0 173
michael@0 174 def onStderr(self, text):
michael@0 175 self.stderr += text
michael@0 176
michael@0 177 def onFinished(self, returncode):
michael@0 178 self.returncode = returncode
michael@0 179 if OPTIONS.show_output:
michael@0 180 self.summary.interleave_output(lambda: self.show_output(sys.stdout))
michael@0 181 if returncode != 0:
michael@0 182 self.summary.failed(self)
michael@0 183 else:
michael@0 184 self.summary.passed(self)
michael@0 185
michael@0 186 def onTimeout(self):
michael@0 187 self.summary.timeout(self)
michael@0 188
michael@0 189 def show_cmd(self, out):
michael@0 190 print "Command: ", make_shell_cmd(self.cmd())
michael@0 191
michael@0 192 def show_output(self, out):
michael@0 193 if self.stdout:
michael@0 194 out.write('Standard output:')
michael@0 195 out.write('\n' + self.stdout + '\n')
michael@0 196 if self.stderr:
michael@0 197 out.write('Standard error:')
michael@0 198 out.write('\n' + self.stderr + '\n')
michael@0 199
michael@0 200 def show(self, out):
michael@0 201 out.write(self.name + '\n')
michael@0 202 if OPTIONS.write_failure_output:
michael@0 203 out.write('Command: %s\n' % (make_shell_cmd(self.cmd()),))
michael@0 204 self.show_output(out)
michael@0 205 out.write('GDB exit code: %r\n' % (self.returncode,))
michael@0 206
michael@0 207 def find_tests(dir, substring = None):
michael@0 208 ans = []
michael@0 209 for dirpath, dirnames, filenames in os.walk(dir):
michael@0 210 if dirpath == '.':
michael@0 211 continue
michael@0 212 for filename in filenames:
michael@0 213 if not filename.endswith('.py'):
michael@0 214 continue
michael@0 215 test = os.path.join(dirpath, filename)
michael@0 216 if substring is None or substring in os.path.relpath(test, dir):
michael@0 217 ans.append(test)
michael@0 218 return ans
michael@0 219
michael@0 220 def build_test_exec(builddir):
michael@0 221 p = subprocess.check_call(['make', 'gdb-tests'], cwd=builddir)
michael@0 222
michael@0 223 def run_tests(tests, summary):
michael@0 224 pool = TaskPool(tests, job_limit=OPTIONS.workercount, timeout=OPTIONS.timeout)
michael@0 225 pool.run_all()
michael@0 226
michael@0 227 OPTIONS = None
michael@0 228 def main(argv):
michael@0 229 global OPTIONS
michael@0 230 script_path = os.path.abspath(__file__)
michael@0 231 script_dir = os.path.dirname(script_path)
michael@0 232
michael@0 233 # LIBDIR is the directory in which we find the SpiderMonkey shared
michael@0 234 # library, to link against.
michael@0 235 #
michael@0 236 # The [TESTS] optional arguments are paths of test files relative
michael@0 237 # to the jit-test/tests directory.
michael@0 238 from optparse import OptionParser
michael@0 239 op = OptionParser(usage='%prog [options] LIBDIR [TESTS...]')
michael@0 240 op.add_option('-s', '--show-cmd', dest='show_cmd', action='store_true',
michael@0 241 help='show GDB shell command run')
michael@0 242 op.add_option('-o', '--show-output', dest='show_output', action='store_true',
michael@0 243 help='show output from GDB')
michael@0 244 op.add_option('-x', '--exclude', dest='exclude', action='append',
michael@0 245 help='exclude given test dir or path')
michael@0 246 op.add_option('-t', '--timeout', dest='timeout', type=float, default=150.0,
michael@0 247 help='set test timeout in seconds')
michael@0 248 op.add_option('-j', '--worker-count', dest='workercount', type=int,
michael@0 249 help='Run [WORKERCOUNT] tests at a time')
michael@0 250 op.add_option('--no-progress', dest='hide_progress', action='store_true',
michael@0 251 help='hide progress bar')
michael@0 252 op.add_option('--worklist', dest='worklist', metavar='FILE',
michael@0 253 help='Read tests to run from [FILE] (or run all if [FILE] not found);\n'
michael@0 254 'write failures back to [FILE]')
michael@0 255 op.add_option('-r', '--read-tests', dest='read_tests', metavar='FILE',
michael@0 256 help='Run test files listed in [FILE]')
michael@0 257 op.add_option('-w', '--write-failures', dest='write_failures', metavar='FILE',
michael@0 258 help='Write failing tests to [FILE]')
michael@0 259 op.add_option('--write-failure-output', dest='write_failure_output', action='store_true',
michael@0 260 help='With --write-failures=FILE, additionally write the output of failed tests to [FILE]')
michael@0 261 op.add_option('--gdb', dest='gdb_executable', metavar='EXECUTABLE', default='gdb',
michael@0 262 help='Run tests with [EXECUTABLE], rather than plain \'gdb\'.')
michael@0 263 op.add_option('--srcdir', dest='srcdir',
michael@0 264 default=os.path.abspath(os.path.join(script_dir, '..')),
michael@0 265 help='Use SpiderMonkey sources in [SRCDIR].')
michael@0 266 op.add_option('--testdir', dest='testdir', default=os.path.join(script_dir, 'tests'),
michael@0 267 help='Find tests in [TESTDIR].')
michael@0 268 op.add_option('--builddir', dest='builddir',
michael@0 269 help='Build test executable in [BUILDDIR].')
michael@0 270 (OPTIONS, args) = op.parse_args(argv)
michael@0 271 if len(args) < 1:
michael@0 272 op.error('missing LIBDIR argument')
michael@0 273 OPTIONS.libdir = os.path.abspath(args[0])
michael@0 274 test_args = args[1:]
michael@0 275
michael@0 276 if not OPTIONS.workercount:
michael@0 277 OPTIONS.workercount = get_cpu_count()
michael@0 278
michael@0 279 # Compute default for OPTIONS.builddir now, since we've computed OPTIONS.libdir.
michael@0 280 if not OPTIONS.builddir:
michael@0 281 OPTIONS.builddir = os.path.join(OPTIONS.libdir, 'gdb')
michael@0 282
michael@0 283 test_set = set()
michael@0 284
michael@0 285 # All the various sources of test names accumulate.
michael@0 286 if test_args:
michael@0 287 for arg in test_args:
michael@0 288 test_set.update(find_tests(OPTIONS.testdir, arg))
michael@0 289 if OPTIONS.worklist:
michael@0 290 try:
michael@0 291 with open(OPTIONS.worklist) as f:
michael@0 292 for line in f:
michael@0 293 test_set.update(os.path.join(test_dir, line.strip('\n')))
michael@0 294 except IOError:
michael@0 295 # With worklist, a missing file means to start the process with
michael@0 296 # the complete list of tests.
michael@0 297 sys.stderr.write("Couldn't read worklist file '%s'; running all tests\n"
michael@0 298 % (OPTIONS.worklist,))
michael@0 299 test_set = set(find_tests(OPTIONS.testdir))
michael@0 300 if OPTIONS.read_tests:
michael@0 301 try:
michael@0 302 with open(OPTIONS.read_tests) as f:
michael@0 303 for line in f:
michael@0 304 test_set.update(os.path.join(test_dir, line.strip('\n')))
michael@0 305 except IOError as err:
michael@0 306 sys.stderr.write("Error trying to read test file '%s': %s\n"
michael@0 307 % (OPTIONS.read_tests, err))
michael@0 308 sys.exit(1)
michael@0 309
michael@0 310 # If none of the above options were passed, and no tests were listed
michael@0 311 # explicitly, use the complete set.
michael@0 312 if not test_args and not OPTIONS.worklist and not OPTIONS.read_tests:
michael@0 313 test_set = set(find_tests(OPTIONS.testdir))
michael@0 314
michael@0 315 if OPTIONS.exclude:
michael@0 316 exclude_set = set()
michael@0 317 for exclude in OPTIONS.exclude:
michael@0 318 exclude_set.update(find_tests(test_dir, exclude))
michael@0 319 test_set -= exclude_set
michael@0 320
michael@0 321 if not test_set:
michael@0 322 sys.stderr.write("No tests found matching command line arguments.\n")
michael@0 323 sys.exit(1)
michael@0 324
michael@0 325 summary = Summary(len(test_set))
michael@0 326 test_list = [ Test(_, summary) for _ in sorted(test_set) ]
michael@0 327
michael@0 328 # Build the test executable from all the .cpp files found in the test
michael@0 329 # directory tree.
michael@0 330 try:
michael@0 331 build_test_exec(OPTIONS.builddir)
michael@0 332 except subprocess.CalledProcessError as err:
michael@0 333 sys.stderr.write("Error building test executable: %s\n" % (err,))
michael@0 334 sys.exit(1)
michael@0 335
michael@0 336 # Run the tests.
michael@0 337 try:
michael@0 338 summary.start()
michael@0 339 run_tests(test_list, summary)
michael@0 340 summary.finish()
michael@0 341 except OSError as err:
michael@0 342 sys.stderr.write("Error running tests: %s\n" % (err,))
michael@0 343 sys.exit(1)
michael@0 344
michael@0 345 sys.exit(0)
michael@0 346
michael@0 347 if __name__ == '__main__':
michael@0 348 main(sys.argv[1:])

mercurial