layout/tools/reftest/mach_commands.py

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

Integrate suggestion from review to improve consistency with existing code.

michael@0 1 # This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 # License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 4
michael@0 5 from __future__ import unicode_literals
michael@0 6
michael@0 7 import mozpack.path
michael@0 8 import os
michael@0 9 import re
michael@0 10 import sys
michael@0 11 import warnings
michael@0 12 import which
michael@0 13
michael@0 14 from mozbuild.base import (
michael@0 15 MachCommandBase,
michael@0 16 MachCommandConditions as conditions,
michael@0 17 MozbuildObject,
michael@0 18 )
michael@0 19
michael@0 20 from mach.decorators import (
michael@0 21 CommandArgument,
michael@0 22 CommandProvider,
michael@0 23 Command,
michael@0 24 )
michael@0 25
michael@0 26
michael@0 27 DEBUGGER_HELP = 'Debugger binary to run test in. Program name or path.'
michael@0 28
michael@0 29 ADB_NOT_FOUND = '''
michael@0 30 The %s command requires the adb binary to be on your path.
michael@0 31
michael@0 32 If you have a B2G build, this can be found in
michael@0 33 '%s/out/host/<platform>/bin'.
michael@0 34 '''.lstrip()
michael@0 35
michael@0 36 GAIA_PROFILE_NOT_FOUND = '''
michael@0 37 The %s command requires a non-debug gaia profile. Either pass in --profile,
michael@0 38 or set the GAIA_PROFILE environment variable.
michael@0 39
michael@0 40 If you do not have a non-debug gaia profile, you can build one:
michael@0 41 $ git clone https://github.com/mozilla-b2g/gaia
michael@0 42 $ cd gaia
michael@0 43 $ make
michael@0 44
michael@0 45 The profile should be generated in a directory called 'profile'.
michael@0 46 '''.lstrip()
michael@0 47
michael@0 48 GAIA_PROFILE_IS_DEBUG = '''
michael@0 49 The %s command requires a non-debug gaia profile. The specified profile,
michael@0 50 %s, is a debug profile.
michael@0 51
michael@0 52 If you do not have a non-debug gaia profile, you can build one:
michael@0 53 $ git clone https://github.com/mozilla-b2g/gaia
michael@0 54 $ cd gaia
michael@0 55 $ make
michael@0 56
michael@0 57 The profile should be generated in a directory called 'profile'.
michael@0 58 '''.lstrip()
michael@0 59
michael@0 60 MARIONETTE_DISABLED = '''
michael@0 61 The %s command requires a marionette enabled build.
michael@0 62
michael@0 63 Add 'ENABLE_MARIONETTE=1' to your mozconfig file and re-build the application.
michael@0 64 Your currently active mozconfig is %s.
michael@0 65 '''.lstrip()
michael@0 66
michael@0 67 class ReftestRunner(MozbuildObject):
michael@0 68 """Easily run reftests.
michael@0 69
michael@0 70 This currently contains just the basics for running reftests. We may want
michael@0 71 to hook up result parsing, etc.
michael@0 72 """
michael@0 73 def __init__(self, *args, **kwargs):
michael@0 74 MozbuildObject.__init__(self, *args, **kwargs)
michael@0 75
michael@0 76 # TODO Bug 794506 remove once mach integrates with virtualenv.
michael@0 77 build_path = os.path.join(self.topobjdir, 'build')
michael@0 78 if build_path not in sys.path:
michael@0 79 sys.path.append(build_path)
michael@0 80
michael@0 81 self.tests_dir = os.path.join(self.topobjdir, '_tests')
michael@0 82 self.reftest_dir = os.path.join(self.tests_dir, 'reftest')
michael@0 83
michael@0 84 def _manifest_file(self, suite):
michael@0 85 """Returns the manifest file used for a given test suite."""
michael@0 86 files = {
michael@0 87 'reftest': 'reftest.list',
michael@0 88 'reftest-ipc': 'reftest.list',
michael@0 89 'crashtest': 'crashtests.list',
michael@0 90 'crashtest-ipc': 'crashtests.list',
michael@0 91 }
michael@0 92 assert suite in files
michael@0 93 return files[suite]
michael@0 94
michael@0 95 def _find_manifest(self, suite, test_file):
michael@0 96 assert test_file
michael@0 97 path_arg = self._wrap_path_argument(test_file)
michael@0 98 relpath = path_arg.relpath()
michael@0 99
michael@0 100 if os.path.isdir(path_arg.srcdir_path()):
michael@0 101 return mozpack.path.join(relpath, self._manifest_file(suite))
michael@0 102
michael@0 103 if relpath.endswith('.list'):
michael@0 104 return relpath
michael@0 105
michael@0 106 raise Exception('Running a single test is not currently supported')
michael@0 107
michael@0 108 def _make_shell_string(self, s):
michael@0 109 return "'%s'" % re.sub("'", r"'\''", s)
michael@0 110
michael@0 111 def run_b2g_test(self, b2g_home=None, xre_path=None, test_file=None,
michael@0 112 suite=None, **kwargs):
michael@0 113 """Runs a b2g reftest.
michael@0 114
michael@0 115 test_file is a path to a test file. It can be a relative path from the
michael@0 116 top source directory, an absolute filename, or a directory containing
michael@0 117 test files.
michael@0 118
michael@0 119 suite is the type of reftest to run. It can be one of ('reftest',
michael@0 120 'crashtest').
michael@0 121 """
michael@0 122 if suite not in ('reftest', 'crashtest'):
michael@0 123 raise Exception('None or unrecognized reftest suite type.')
michael@0 124
michael@0 125 # Find the manifest file
michael@0 126 if not test_file:
michael@0 127 if suite == 'reftest':
michael@0 128 test_file = mozpack.path.join('layout', 'reftests')
michael@0 129 elif suite == 'crashtest':
michael@0 130 test_file = mozpack.path.join('testing', 'crashtest')
michael@0 131
michael@0 132 if not os.path.exists(os.path.join(self.topsrcdir, test_file)):
michael@0 133 test_file = mozpack.path.relpath(os.path.abspath(test_file),
michael@0 134 self.topsrcdir)
michael@0 135
michael@0 136 manifest = self._find_manifest(suite, test_file)
michael@0 137 if not os.path.exists(mozpack.path.join(self.topsrcdir, manifest)):
michael@0 138 raise Exception('No manifest file was found at %s.' % manifest)
michael@0 139
michael@0 140 # Need to chdir to reftest_dir otherwise imports fail below.
michael@0 141 os.chdir(self.reftest_dir)
michael@0 142
michael@0 143 # The imp module can spew warnings if the modules below have
michael@0 144 # already been imported, ignore them.
michael@0 145 with warnings.catch_warnings():
michael@0 146 warnings.simplefilter('ignore')
michael@0 147
michael@0 148 import imp
michael@0 149 path = os.path.join(self.reftest_dir, 'runreftestb2g.py')
michael@0 150 with open(path, 'r') as fh:
michael@0 151 imp.load_module('reftest', fh, path, ('.py', 'r', imp.PY_SOURCE))
michael@0 152 import reftest
michael@0 153
michael@0 154 # Set up the reftest options.
michael@0 155 parser = reftest.B2GOptions()
michael@0 156 options, args = parser.parse_args([])
michael@0 157
michael@0 158 # Tests need to be served from a subdirectory of the server. Symlink
michael@0 159 # topsrcdir here to get around this.
michael@0 160 tests = os.path.join(self.reftest_dir, 'tests')
michael@0 161 if not os.path.isdir(tests):
michael@0 162 os.symlink(self.topsrcdir, tests)
michael@0 163 args.insert(0, os.path.join('tests', manifest))
michael@0 164
michael@0 165 for k, v in kwargs.iteritems():
michael@0 166 setattr(options, k, v)
michael@0 167
michael@0 168 if conditions.is_b2g_desktop(self):
michael@0 169 if self.substs.get('ENABLE_MARIONETTE') != '1':
michael@0 170 print(MARIONETTE_DISABLED % ('mochitest-b2g-desktop',
michael@0 171 self.mozconfig['path']))
michael@0 172 return 1
michael@0 173
michael@0 174 options.profile = options.profile or os.environ.get('GAIA_PROFILE')
michael@0 175 if not options.profile:
michael@0 176 print(GAIA_PROFILE_NOT_FOUND % 'reftest-b2g-desktop')
michael@0 177 return 1
michael@0 178
michael@0 179 if os.path.isfile(os.path.join(options.profile, 'extensions', \
michael@0 180 'httpd@gaiamobile.org')):
michael@0 181 print(GAIA_PROFILE_IS_DEBUG % ('mochitest-b2g-desktop',
michael@0 182 options.profile))
michael@0 183 return 1
michael@0 184
michael@0 185 options.desktop = True
michael@0 186 options.app = self.get_binary_path()
michael@0 187 if not options.app.endswith('-bin'):
michael@0 188 options.app = '%s-bin' % options.app
michael@0 189 if not os.path.isfile(options.app):
michael@0 190 options.app = options.app[:-len('-bin')]
michael@0 191
michael@0 192 return reftest.run_desktop_reftests(parser, options, args)
michael@0 193
michael@0 194
michael@0 195 try:
michael@0 196 which.which('adb')
michael@0 197 except which.WhichError:
michael@0 198 # TODO Find adb automatically if it isn't on the path
michael@0 199 raise Exception(ADB_NOT_FOUND % ('%s-remote' % suite, b2g_home))
michael@0 200
michael@0 201 options.b2gPath = b2g_home
michael@0 202 options.logcat_dir = self.reftest_dir
michael@0 203 options.httpdPath = os.path.join(self.topsrcdir, 'netwerk', 'test', 'httpserver')
michael@0 204 options.xrePath = xre_path
michael@0 205 options.ignoreWindowSize = True
michael@0 206 return reftest.run_remote_reftests(parser, options, args)
michael@0 207
michael@0 208 def run_desktop_test(self, test_file=None, filter=None, suite=None,
michael@0 209 debugger=None, parallel=False, shuffle=False,
michael@0 210 e10s=False, this_chunk=None, total_chunks=None):
michael@0 211 """Runs a reftest.
michael@0 212
michael@0 213 test_file is a path to a test file. It can be a relative path from the
michael@0 214 top source directory, an absolute filename, or a directory containing
michael@0 215 test files.
michael@0 216
michael@0 217 filter is a regular expression (in JS syntax, as could be passed to the
michael@0 218 RegExp constructor) to select which reftests to run from the manifest.
michael@0 219
michael@0 220 suite is the type of reftest to run. It can be one of ('reftest',
michael@0 221 'crashtest').
michael@0 222
michael@0 223 debugger is the program name (in $PATH) or the full path of the
michael@0 224 debugger to run.
michael@0 225
michael@0 226 parallel indicates whether tests should be run in parallel or not.
michael@0 227
michael@0 228 shuffle indicates whether to run tests in random order.
michael@0 229 """
michael@0 230
michael@0 231 if suite not in ('reftest', 'reftest-ipc', 'crashtest', 'crashtest-ipc'):
michael@0 232 raise Exception('None or unrecognized reftest suite type.')
michael@0 233
michael@0 234 env = {}
michael@0 235 extra_args = []
michael@0 236
michael@0 237 if test_file:
michael@0 238 path = self._find_manifest(suite, test_file)
michael@0 239 if not os.path.exists(mozpack.path.join(self.topsrcdir, path)):
michael@0 240 raise Exception('No manifest file was found at %s.' % path)
michael@0 241 env[b'TEST_PATH'] = path
michael@0 242 if filter:
michael@0 243 extra_args.extend(['--filter', self._make_shell_string(filter)])
michael@0 244
michael@0 245 pass_thru = False
michael@0 246
michael@0 247 if debugger:
michael@0 248 extra_args.append('--debugger=%s' % debugger)
michael@0 249 pass_thru = True
michael@0 250
michael@0 251 if parallel:
michael@0 252 extra_args.append('--run-tests-in-parallel')
michael@0 253
michael@0 254 if shuffle:
michael@0 255 extra_args.append('--shuffle')
michael@0 256
michael@0 257 if e10s:
michael@0 258 extra_args.append('--e10s')
michael@0 259
michael@0 260 if this_chunk:
michael@0 261 extra_args.append('--this-chunk=%s' % this_chunk)
michael@0 262
michael@0 263 if total_chunks:
michael@0 264 extra_args.append('--total-chunks=%s' % total_chunks)
michael@0 265
michael@0 266 if extra_args:
michael@0 267 args = [os.environ.get(b'EXTRA_TEST_ARGS', '')]
michael@0 268 args.extend(extra_args)
michael@0 269 env[b'EXTRA_TEST_ARGS'] = ' '.join(args)
michael@0 270
michael@0 271 # TODO hook up harness via native Python
michael@0 272 return self._run_make(directory='.', target=suite, append_env=env,
michael@0 273 pass_thru=pass_thru, ensure_exit_code=False)
michael@0 274
michael@0 275
michael@0 276 def ReftestCommand(func):
michael@0 277 """Decorator that adds shared command arguments to reftest commands."""
michael@0 278
michael@0 279 debugger = CommandArgument('--debugger', metavar='DEBUGGER',
michael@0 280 help=DEBUGGER_HELP)
michael@0 281 func = debugger(func)
michael@0 282
michael@0 283 flter = CommandArgument('--filter', metavar='REGEX',
michael@0 284 help='A JS regular expression to match test URLs against, to select '
michael@0 285 'a subset of tests to run.')
michael@0 286 func = flter(func)
michael@0 287
michael@0 288 path = CommandArgument('test_file', nargs='?', metavar='MANIFEST',
michael@0 289 help='Reftest manifest file, or a directory in which to select '
michael@0 290 'reftest.list. If omitted, the entire test suite is executed.')
michael@0 291 func = path(func)
michael@0 292
michael@0 293 parallel = CommandArgument('--parallel', action='store_true',
michael@0 294 help='Run tests in parallel.')
michael@0 295 func = parallel(func)
michael@0 296
michael@0 297 shuffle = CommandArgument('--shuffle', action='store_true',
michael@0 298 help='Run tests in random order.')
michael@0 299 func = shuffle(func)
michael@0 300
michael@0 301 e10s = CommandArgument('--e10s', action='store_true',
michael@0 302 help='Use content processes.')
michael@0 303 func = e10s(func)
michael@0 304
michael@0 305 totalChunks = CommandArgument('--total-chunks',
michael@0 306 help = 'How many chunks to split the tests up into.')
michael@0 307 func = totalChunks(func)
michael@0 308
michael@0 309 thisChunk = CommandArgument('--this-chunk',
michael@0 310 help = 'Which chunk to run between 1 and --total-chunks.')
michael@0 311 func = thisChunk(func)
michael@0 312
michael@0 313 return func
michael@0 314
michael@0 315 def B2GCommand(func):
michael@0 316 """Decorator that adds shared command arguments to b2g mochitest commands."""
michael@0 317
michael@0 318 busybox = CommandArgument('--busybox', default=None,
michael@0 319 help='Path to busybox binary to install on device')
michael@0 320 func = busybox(func)
michael@0 321
michael@0 322 logcatdir = CommandArgument('--logcat-dir', default=None,
michael@0 323 help='directory to store logcat dump files')
michael@0 324 func = logcatdir(func)
michael@0 325
michael@0 326 geckopath = CommandArgument('--gecko-path', default=None,
michael@0 327 help='the path to a gecko distribution that should \
michael@0 328 be installed on the emulator prior to test')
michael@0 329 func = geckopath(func)
michael@0 330
michael@0 331 sdcard = CommandArgument('--sdcard', default="10MB",
michael@0 332 help='Define size of sdcard: 1MB, 50MB...etc')
michael@0 333 func = sdcard(func)
michael@0 334
michael@0 335 emulator_res = CommandArgument('--emulator-res', default='800x1000',
michael@0 336 help='Emulator resolution of the format \'<width>x<height>\'')
michael@0 337 func = emulator_res(func)
michael@0 338
michael@0 339 emulator = CommandArgument('--emulator', default='arm',
michael@0 340 help='Architecture of emulator to use: x86 or arm')
michael@0 341 func = emulator(func)
michael@0 342
michael@0 343 marionette = CommandArgument('--marionette', default=None,
michael@0 344 help='host:port to use when connecting to Marionette')
michael@0 345 func = marionette(func)
michael@0 346
michael@0 347 totalChunks = CommandArgument('--total-chunks', dest='totalChunks',
michael@0 348 help = 'How many chunks to split the tests up into.')
michael@0 349 func = totalChunks(func)
michael@0 350
michael@0 351 thisChunk = CommandArgument('--this-chunk', dest='thisChunk',
michael@0 352 help = 'Which chunk to run between 1 and --total-chunks.')
michael@0 353 func = thisChunk(func)
michael@0 354
michael@0 355 path = CommandArgument('test_file', default=None, nargs='?',
michael@0 356 metavar='TEST',
michael@0 357 help='Test to run. Can be specified as a single file, a ' \
michael@0 358 'directory, or omitted. If omitted, the entire test suite is ' \
michael@0 359 'executed.')
michael@0 360 func = path(func)
michael@0 361
michael@0 362 return func
michael@0 363
michael@0 364
michael@0 365 @CommandProvider
michael@0 366 class MachCommands(MachCommandBase):
michael@0 367 @Command('reftest', category='testing', description='Run reftests.')
michael@0 368 @ReftestCommand
michael@0 369 def run_reftest(self, test_file, **kwargs):
michael@0 370 return self._run_reftest(test_file, suite='reftest', **kwargs)
michael@0 371
michael@0 372 @Command('reftest-ipc', category='testing',
michael@0 373 description='Run IPC reftests.')
michael@0 374 @ReftestCommand
michael@0 375 def run_ipc(self, test_file, **kwargs):
michael@0 376 return self._run_reftest(test_file, suite='reftest-ipc', **kwargs)
michael@0 377
michael@0 378 @Command('crashtest', category='testing',
michael@0 379 description='Run crashtests.')
michael@0 380 @ReftestCommand
michael@0 381 def run_crashtest(self, test_file, **kwargs):
michael@0 382 return self._run_reftest(test_file, suite='crashtest', **kwargs)
michael@0 383
michael@0 384 @Command('crashtest-ipc', category='testing',
michael@0 385 description='Run IPC crashtests.')
michael@0 386 @ReftestCommand
michael@0 387 def run_crashtest_ipc(self, test_file, **kwargs):
michael@0 388 return self._run_reftest(test_file, suite='crashtest-ipc', **kwargs)
michael@0 389
michael@0 390 def _run_reftest(self, test_file=None, suite=None, **kwargs):
michael@0 391 reftest = self._spawn(ReftestRunner)
michael@0 392 return reftest.run_desktop_test(test_file, suite=suite, **kwargs)
michael@0 393
michael@0 394
michael@0 395 # TODO For now b2g commands will only work with the emulator,
michael@0 396 # they should be modified to work with all devices.
michael@0 397 def is_emulator(cls):
michael@0 398 """Emulator needs to be configured."""
michael@0 399 return cls.device_name.find('emulator') == 0
michael@0 400
michael@0 401
michael@0 402 @CommandProvider
michael@0 403 class B2GCommands(MachCommandBase):
michael@0 404 def __init__(self, context):
michael@0 405 MachCommandBase.__init__(self, context)
michael@0 406
michael@0 407 for attr in ('b2g_home', 'xre_path', 'device_name'):
michael@0 408 setattr(self, attr, getattr(context, attr, None))
michael@0 409
michael@0 410 @Command('reftest-remote', category='testing',
michael@0 411 description='Run a remote reftest.',
michael@0 412 conditions=[conditions.is_b2g, is_emulator])
michael@0 413 @B2GCommand
michael@0 414 def run_reftest_remote(self, test_file, **kwargs):
michael@0 415 return self._run_reftest(test_file, suite='reftest', **kwargs)
michael@0 416
michael@0 417 @Command('reftest-b2g-desktop', category='testing',
michael@0 418 description='Run a b2g desktop reftest.',
michael@0 419 conditions=[conditions.is_b2g_desktop])
michael@0 420 @B2GCommand
michael@0 421 def run_reftest_b2g_desktop(self, test_file, **kwargs):
michael@0 422 return self._run_reftest(test_file, suite='reftest', **kwargs)
michael@0 423
michael@0 424 @Command('crashtest-remote', category='testing',
michael@0 425 description='Run a remote crashtest.',
michael@0 426 conditions=[conditions.is_b2g, is_emulator])
michael@0 427 @B2GCommand
michael@0 428 def run_crashtest_remote(self, test_file, **kwargs):
michael@0 429 return self._run_reftest(test_file, suite='crashtest', **kwargs)
michael@0 430
michael@0 431 def _run_reftest(self, test_file=None, suite=None, **kwargs):
michael@0 432 reftest = self._spawn(ReftestRunner)
michael@0 433 return reftest.run_b2g_test(self.b2g_home, self.xre_path,
michael@0 434 test_file, suite=suite, **kwargs)

mercurial