author | Wes Kocher <wkocher@mozilla.com> |
Thu, 19 Nov 2015 11:54:17 -0800 | |
changeset 273403 | d69c0607d8b1702009b059e71e1558ad13102919 |
parent 273402 | 39db40982d31fe595c259394cae7bf6f72a3e762 |
child 273404 | 3df173f3761d4dc2bd77ba43ffa503545456b80c |
push id | 29702 |
push user | cbook@mozilla.com |
push date | Fri, 20 Nov 2015 12:13:22 +0000 |
treeherder | mozilla-central@ec628289d8b4 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
bugs | 1225903 |
milestone | 45.0a1 |
backs out | 6ab2285938201fecc0c06d083e71dc0ec17229e0 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/testing/mochitest/mach_commands.py +++ b/testing/mochitest/mach_commands.py @@ -25,16 +25,40 @@ from mach.decorators import ( CommandProvider, Command, ) import mozpack.path as mozpath here = os.path.abspath(os.path.dirname(__file__)) +GAIA_PROFILE_NOT_FOUND = ''' +The mochitest command requires a non-debug gaia profile. Either +pass in --profile, or set the GAIA_PROFILE environment variable. + +If you do not have a non-debug gaia profile, you can build one: + $ git clone https://github.com/mozilla-b2g/gaia + $ cd gaia + $ make + +The profile should be generated in a directory called 'profile'. +'''.lstrip() + +GAIA_PROFILE_IS_DEBUG = ''' +The mochitest command requires a non-debug gaia profile. The +specified profile, {}, is a debug profile. + +If you do not have a non-debug gaia profile, you can build one: + $ git clone https://github.com/mozilla-b2g/gaia + $ cd gaia + $ make + +The profile should be generated in a directory called 'profile'. +'''.lstrip() + ENG_BUILD_REQUIRED = ''' The mochitest command requires an engineering build. It may be the case that VARIANT=user or PRODUCTION=1 were set. Try re-building with VARIANT=eng: $ VARIANT=eng ./build.sh There should be an app called 'test-container.gaiamobile.org' located in {}. @@ -70,17 +94,17 @@ NOW_RUNNING = ''' ''' # Maps test flavors to data needed to run them ALL_FLAVORS = { 'mochitest': { 'suite': 'plain', 'aliases': ('plain', 'mochitest'), - 'enabled_apps': ('firefox', 'b2g', 'android', 'mulet'), + 'enabled_apps': ('firefox', 'b2g', 'android', 'mulet', 'b2g_desktop'), }, 'chrome': { 'suite': 'chrome', 'aliases': ('chrome', 'mochitest-chrome'), 'enabled_apps': ('firefox', 'mulet', 'b2g', 'android'), 'extra_args': { 'chrome': True, } @@ -130,17 +154,17 @@ ALL_FLAVORS = { 'aliases': ('webapprt-content', 'mochitest-webapprt-content'), 'enabled_apps': ('firefox',), 'extra_args': { 'webapprtContent': True, } }, } -SUPPORTED_APPS = ['firefox', 'b2g', 'android', 'mulet'] +SUPPORTED_APPS = ['firefox', 'b2g', 'android', 'mulet', 'b2g_desktop'] SUPPORTED_FLAVORS = list(chain.from_iterable([f['aliases'] for f in ALL_FLAVORS.values()])) CANONICAL_FLAVORS = sorted([f['aliases'][0] for f in ALL_FLAVORS.values()]) class MochitestRunner(MozbuildObject): """Easily run mochitests. @@ -207,17 +231,27 @@ class MochitestRunner(MozbuildObject): from mozbuild.testing import TestResolver resolver = self._spawn(TestResolver) tests = list(resolver.resolve_tests(paths=test_paths, cwd=cwd)) return tests def run_b2g_test(self, context, tests=None, suite='mochitest', **kwargs): """Runs a b2g mochitest.""" - if context.target_out: + if kwargs.get('desktop'): + kwargs['profile'] = kwargs.get('profile') or os.environ.get('GAIA_PROFILE') + if not kwargs['profile'] or not os.path.isdir(kwargs['profile']): + print(GAIA_PROFILE_NOT_FOUND) + sys.exit(1) + + if os.path.isfile(os.path.join(kwargs['profile'], 'extensions', + 'httpd@gaiamobile.org')): + print(GAIA_PROFILE_IS_DEBUG.format(kwargs['profile'])) + sys.exit(1) + elif context.target_out: host_webapps_dir = os.path.join(context.target_out, 'data', 'local', 'webapps') if not os.path.isdir(os.path.join( host_webapps_dir, 'test-container.gaiamobile.org')): print(ENG_BUILD_REQUIRED.format(host_webapps_dir)) sys.exit(1) # TODO without os.chdir, chained imports fail below os.chdir(self.mochitest_dir) @@ -238,17 +272,20 @@ class MochitestRunner(MozbuildObject): options = Namespace(**kwargs) from manifestparser import TestManifest if tests: manifest = TestManifest() manifest.tests.extend(tests) options.manifestFile = manifest - return mochitest.run_test_harness(options) + if options.desktop: + return mochitest.run_desktop_mochitests(options) + + return mochitest.run_remote_mochitests(options) def run_desktop_test(self, context, tests=None, suite=None, **kwargs): """Runs a mochitest. suite is the type of mochitest to run. It can be one of ('plain', 'chrome', 'browser', 'a11y', 'jetpack-package', 'jetpack-addon', 'webapprt-chrome', 'webapprt-content'). """ @@ -520,17 +557,17 @@ class MachCommands(MachCommandBase): reason = 'requires {}'.format(' or '.join(apps)) else: reason = 'excluded by the command line' msg.append(' mochitest -f {} ({})'.format(name, reason)) print(SUPPORTED_TESTS_NOT_FOUND.format( buildapp, '\n'.join(sorted(msg)))) return 1 - if buildapp in ('b2g',): + if buildapp in ('b2g', 'b2g_desktop'): run_mochitest = mochitest.run_b2g_test elif buildapp == 'android': run_mochitest = mochitest.run_android_test else: run_mochitest = mochitest.run_desktop_test overall = None for (flavor, subsuite), tests in sorted(suites.items()):
--- a/testing/mochitest/mochitest_options.py +++ b/testing/mochitest/mochitest_options.py @@ -591,17 +591,17 @@ class MochitestArguments(ArgumentContain os.path.join(build_obj.bindir, *p) for p in gmp_modules) if options.totalChunks is not None and options.thisChunk is None: parser.error( "thisChunk must be specified when totalChunks is specified") if options.extra_mozinfo_json: if not os.path.isfile(options.extra_mozinfo_json): - parser.error("Error: couldn't find mozinfo.json at '%s'." + parser.error("Error: couldn't find mozinfo.json at '%s'."\ % options.extra_mozinfo_json) options.extra_mozinfo_json = json.load(open(options.extra_mozinfo_json)) if options.totalChunks: if not 1 <= options.thisChunk <= options.totalChunks: parser.error("thisChunk must be between 1 and totalChunks") @@ -774,31 +774,37 @@ class MochitestArguments(ArgumentContain # XXX We can't normalize test_paths in the non build_obj case here, # because testRoot depends on the flavor, which is determined by the # mach command and therefore not finalized yet. Conversely, test paths # need to be normalized here for the mach case. if options.test_paths and build_obj: # Normalize test paths so they are relative to test root options.test_paths = [build_obj._wrap_path_argument(p).relpath() - for p in options.test_paths] + for p in options.test_paths] return options class B2GArguments(ArgumentContainer): """B2G specific arguments.""" args = [ [["--b2gpath"], {"dest": "b2gPath", "default": None, "help": "Path to B2G repo or QEMU directory.", "suppress": True, }], + [["--desktop"], + {"action": "store_true", + "default": False, + "help": "Run the tests on a B2G desktop build.", + "suppress": True, + }], [["--marionette"], {"default": None, "help": "host:port to use when connecting to Marionette", }], [["--emulator"], {"default": None, "help": "Architecture of emulator to use, x86 or arm", "suppress": True, @@ -862,16 +868,21 @@ class B2GArguments(ArgumentContainer): }], [["--gecko-path"], {"dest": "geckoPath", "default": None, "help": "The path to a gecko distribution that should be installed on the emulator " "prior to test.", "suppress": True, }], + [["--profile"], + {"dest": "profile", + "default": None, + "help": "For desktop testing, the path to the gaia profile to use.", + }], [["--logdir"], {"dest": "logdir", "default": None, "help": "Directory to store log files.", }], [['--busybox'], {"dest": 'busybox', "default": None, @@ -893,16 +904,30 @@ class B2GArguments(ArgumentContainer): 'extensionsToExclude': ['specialpowers'], # See dependencies of bug 1038943. 'defaultLeakThreshold': 5536, } def validate(self, parser, options, context): """Validate b2g options.""" + if options.desktop and not options.app: + if not (build_obj and conditions.is_b2g_desktop(build_obj)): + parser.error( + "--desktop specified, but no b2g desktop build detected! Either " + "build for b2g desktop, or point --appname to a b2g desktop binary.") + elif build_obj and conditions.is_b2g_desktop(build_obj): + options.desktop = True + if not options.app: + options.app = build_obj.get_binary_path() + if not options.app.endswith('-bin'): + options.app = '%s-bin' % options.app + if not os.path.isfile(options.app): + options.app = options.app[:-len('-bin')] + if options.remoteWebServer is None: if os.name != "nt": options.remoteWebServer = moznetwork.get_ip() else: parser.error( "You must specify a --remote-webserver=<ip address>") options.webServer = options.remoteWebServer @@ -1163,17 +1188,17 @@ class MochitestArgumentParser(ArgumentPa def __init__(self, app=None, **kwargs): ArgumentParser.__init__(self, usage=self.__doc__, conflict_handler='resolve', **kwargs) self.oldcwd = os.getcwd() self.app = app if not self.app and build_obj: if conditions.is_android(build_obj): self.app = 'android' - elif conditions.is_b2g(build_obj): + elif conditions.is_b2g(build_obj) or conditions.is_b2g_desktop(build_obj): self.app = 'b2g' if not self.app: # platform can't be determined and app wasn't specified explicitly, # so just use generic arguments and hope for the best self.app = 'generic' if self.app not in container_map: self.error("Unrecognized app '{}'! Must be one of: {}".format(
--- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -487,49 +487,55 @@ class WebSocketServer(object): self._process.run() pid = self._process.pid self._log.info("runtests.py | Websocket server pid: %d" % pid) def stop(self): self._process.kill() -class MochitestBase(object): +class MochitestUtilsMixin(object): + + """ + Class containing some utility functions common to both local and remote + mochitest runners """ - Base mochitest class for both desktop and b2g. - """ + + # TODO Utility classes are a code smell. This class is temporary + # and should be removed when desktop mochitests are refactored + # on top of mozbase. Each of the functions in here should + # probably live somewhere in mozbase oldcwd = os.getcwd() jarDir = 'mochijar' # Path to the test script on the server TEST_PATH = "tests" NESTED_OOP_TEST_PATH = "nested_oop" CHROME_PATH = "redirect.html" urlOpts = [] log = None def __init__(self, logger_options): self.update_mozinfo() self.server = None self.wsserver = None self.sslTunnel = None - self._active_tests = None self._locations = None if self.log is None: commandline.log_formatters["tbpl"] = ( MochitestFormatter, "Mochitest specific tbpl formatter") self.log = commandline.setup_logging("mochitest", logger_options, { "tbpl": sys.stdout }) - MochitestBase.log = self.log + MochitestUtilsMixin.log = self.log self.message_logger = MessageLogger(logger=self.log) def update_mozinfo(self): """walk up directories to find mozinfo.json update the info""" # TODO: This should go in a more generic place, e.g. mozinfo path = SCRIPT_DIR @@ -537,20 +543,16 @@ class MochitestBase(object): while path != os.path.expanduser('~'): if path in dirs: break dirs.add(path) path = os.path.split(path)[0] mozinfo.find_and_update_from_json(*dirs) - def environment(self, **kwargs): - kwargs['log'] = self.log - return test_environment(**kwargs) - def getFullPath(self, path): " Get an absolute path relative to self.oldcwd." return os.path.normpath( os.path.join( self.oldcwd, os.path.expanduser(path))) def getLogFilePath(self, logFile): @@ -987,283 +989,16 @@ overlay chrome://browser/content/browser if os.path.isdir(path) or ( os.path.isfile(path) and path.endswith(".xpi")): extensions.append(path) # append mochikit extensions.append(os.path.join(SCRIPT_DIR, self.jarDir)) return extensions - def logPreamble(self, tests): - """Logs a suite_start message and test_start/test_end at the beginning of a run. - """ - self.log.suite_start([t['path'] for t in tests]) - for test in tests: - if 'disabled' in test: - self.log.test_start(test['path']) - self.log.test_end( - test['path'], - 'SKIP', - message=test['disabled']) - - def getActiveTests(self, options, disabled=True): - """ - This method is used to parse the manifest and return active filtered tests. - """ - if self._active_tests: - return self._active_tests - - manifest = self.getTestManifest(options) - if manifest: - if options.extra_mozinfo_json: - mozinfo.update(options.extra_mozinfo_json) - info = mozinfo.info - - # Bug 1089034 - imptest failure expectations are encoded as - # test manifests, even though they aren't tests. This gross - # hack causes several problems in automation including - # throwing off the chunking numbers. Remove them manually - # until bug 1089034 is fixed. - def remove_imptest_failure_expectations(tests, values): - return (t for t in tests - if 'imptests/failures' not in t['path']) - - filters = [ - remove_imptest_failure_expectations, - subsuite(options.subsuite), - ] - - if options.test_tags: - filters.append(tags(options.test_tags)) - - if options.test_paths: - options.test_paths = self.normalize_paths(options.test_paths) - filters.append(pathprefix(options.test_paths)) - - # Add chunking filters if specified - if options.totalChunks: - if options.chunkByRuntime: - runtime_file = self.resolve_runtime_file(options) - if not os.path.exists(runtime_file): - self.log.warning("runtime file %s not found; defaulting to chunk-by-dir" % - runtime_file) - options.chunkByRuntime = None - flavor = self.getTestFlavor(options) - if flavor in ('browser-chrome', 'devtools-chrome'): - # these values match current mozharness configs - options.chunkbyDir = 5 - else: - options.chunkByDir = 4 - - if options.chunkByDir: - filters.append(chunk_by_dir(options.thisChunk, - options.totalChunks, - options.chunkByDir)) - elif options.chunkByRuntime: - with open(runtime_file, 'r') as f: - runtime_data = json.loads(f.read()) - runtimes = runtime_data['runtimes'] - default = runtime_data['excluded_test_average'] - filters.append( - chunk_by_runtime(options.thisChunk, - options.totalChunks, - runtimes, - default_runtime=default)) - else: - filters.append(chunk_by_slice(options.thisChunk, - options.totalChunks)) - - tests = manifest.active_tests( - exists=False, disabled=disabled, filters=filters, **info) - - if len(tests) == 0: - self.log.error("no tests to run using specified " - "combination of filters: {}".format( - manifest.fmt_filters())) - - paths = [] - for test in tests: - if len(tests) == 1 and 'disabled' in test: - del test['disabled'] - - pathAbs = os.path.abspath(test['path']) - assert pathAbs.startswith(self.testRootAbs) - tp = pathAbs[len(self.testRootAbs):].replace('\\', '/').strip('/') - - if not self.isTest(options, tp): - self.log.warning( - 'Warning: %s from manifest %s is not a valid test' % - (test['name'], test['manifest'])) - continue - - testob = {'path': tp} - if 'disabled' in test: - testob['disabled'] = test['disabled'] - if 'expected' in test: - testob['expected'] = test['expected'] - paths.append(testob) - - def path_sort(ob1, ob2): - path1 = ob1['path'].split('/') - path2 = ob2['path'].split('/') - return cmp(path1, path2) - - paths.sort(path_sort) - self._active_tests = paths - if options.dump_tests: - options.dump_tests = os.path.expanduser(options.dump_tests) - assert os.path.exists(os.path.dirname(options.dump_tests)) - with open(options.dump_tests, 'w') as dumpFile: - dumpFile.write(json.dumps({'active_tests': self._active_tests})) - - self.log.info("Dumping active_tests to %s file." % options.dump_tests) - sys.exit() - - return self._active_tests - - def getTestManifest(self, options): - if isinstance(options.manifestFile, TestManifest): - manifest = options.manifestFile - elif options.manifestFile and os.path.isfile(options.manifestFile): - manifestFileAbs = os.path.abspath(options.manifestFile) - assert manifestFileAbs.startswith(SCRIPT_DIR) - manifest = TestManifest([options.manifestFile], strict=False) - elif options.manifestFile and os.path.isfile(os.path.join(SCRIPT_DIR, options.manifestFile)): - manifestFileAbs = os.path.abspath( - os.path.join( - SCRIPT_DIR, - options.manifestFile)) - assert manifestFileAbs.startswith(SCRIPT_DIR) - manifest = TestManifest([manifestFileAbs], strict=False) - else: - masterName = self.getTestFlavor(options) + '.ini' - masterPath = os.path.join(SCRIPT_DIR, self.testRoot, masterName) - - if os.path.exists(masterPath): - manifest = TestManifest([masterPath], strict=False) - else: - self._log.warning( - 'TestManifest masterPath %s does not exist' % - masterPath) - - return manifest - - def makeTestConfig(self, options): - "Creates a test configuration file for customizing test execution." - options.logFile = options.logFile.replace("\\", "\\\\") - - if "MOZ_HIDE_RESULTS_TABLE" in os.environ and os.environ[ - "MOZ_HIDE_RESULTS_TABLE"] == "1": - options.hideResultsTable = True - - # strip certain unnecessary items to avoid serialization errors in json.dumps() - d = dict((k, v) for k, v in options.__dict__.items() if (v is None) or - isinstance(v, (basestring, numbers.Number))) - d['testRoot'] = self.testRoot - if not options.keep_open: - d['closeWhenDone'] = '1' - content = json.dumps(d) - - with open(os.path.join(options.profilePath, "testConfig.js"), "w") as config: - config.write(content) - - def buildBrowserEnv(self, options, debugger=False, env=None): - """build the environment variables for the specific test and operating system""" - if mozinfo.info["asan"]: - lsanPath = SCRIPT_DIR - else: - lsanPath = None - - browserEnv = self.environment( - xrePath=options.xrePath, - env=env, - debugger=debugger, - dmdPath=options.dmdPath, - lsanPath=lsanPath) - - # These variables are necessary for correct application startup; change - # via the commandline at your own risk. - browserEnv["XPCOM_DEBUG_BREAK"] = "stack" - - # When creating child processes on Windows pre-Vista (e.g. Windows XP) we - # don't normally inherit stdout/err handles, because you can only do it by - # inheriting all other inheritable handles as well. - # We need to inherit them for plain mochitests for test logging purposes, so - # we do so on the basis of a specific environment variable. - if self.getTestFlavor(options) == "mochitest": - browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"] = "1" - - # interpolate environment passed with options - try: - browserEnv.update( - dict( - parseKeyValue( - options.environment, - context='--setenv'))) - except KeyValueParseError as e: - self.log.error(str(e)) - return None - - browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file - - try: - gmp_path = self.getGMPPluginPath(options) - if gmp_path is not None: - browserEnv["MOZ_GMP_PATH"] = gmp_path - except EnvironmentError: - self.log.error('Could not find path to gmp-fake plugin!') - return None - - if options.fatalAssertions: - browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort" - - # Produce an NSPR log, is setup (see NSPR_LOG_MODULES global at the top of - # this script). - self.nsprLogs = NSPR_LOG_MODULES and "MOZ_UPLOAD_DIR" in os.environ - if self.nsprLogs: - browserEnv["NSPR_LOG_MODULES"] = NSPR_LOG_MODULES - - browserEnv["NSPR_LOG_FILE"] = "%s/nspr.log" % tempfile.gettempdir() - browserEnv["GECKO_SEPARATE_NSPR_LOGS"] = "1" - - if debugger and not options.slowscript: - browserEnv["JS_DISABLE_SLOW_SCRIPT_SIGNALS"] = "1" - - # For e10s, our tests default to suppressing the "unsafe CPOW usage" - # warnings that can plague test logs. - if not options.enableCPOWWarnings: - browserEnv["DISABLE_UNSAFE_CPOW_WARNINGS"] = "1" - - return browserEnv - - def killNamedOrphans(self, pname): - """ Kill orphan processes matching the given command name """ - self.log.info("Checking for orphan %s processes..." % pname) - - def _psInfo(line): - if pname in line: - self.log.info(line) - - process = mozprocess.ProcessHandler(['ps', '-f'], - processOutputLine=_psInfo) - process.run() - process.wait() - - def _psKill(line): - parts = line.split() - if len(parts) == 3 and parts[0].isdigit(): - pid = int(parts[0]) - if parts[2] == pname and parts[1] == '1': - self.log.info("killing %s orphan with pid %d" % (pname, pid)) - killPid(pid, self.log) - process = mozprocess.ProcessHandler(['ps', '-o', 'pid,ppid,comm'], - processOutputLine=_psKill) - process.run() - process.wait() - class SSLTunnel: def __init__(self, options, logger, ignoreSSLTunnelExts=False): self.log = logger self.process = None self.utilityPath = options.utilityPath self.xrePath = options.xrePath @@ -1485,31 +1220,29 @@ def parseKeyValue(strings, separator='=' missing = [string for string in strings if separator not in string] if missing: raise KeyValueParseError( "Error: syntax error in %s" % (context, ','.join(missing)), errors=missing) return [string.split(separator, 1) for string in strings] -class MochitestDesktop(MochitestBase): - """ - Mochitest class for desktop firefox and mulet. - """ +class Mochitest(MochitestUtilsMixin): + _active_tests = None certdbNew = False sslTunnel = None DEFAULT_TIMEOUT = 60.0 mediaDevices = None # XXX use automation.py for test name to avoid breaking legacy # TODO: replace this with 'runtests.py' or 'mochitest' or the like test_name = 'automation.py' def __init__(self, logger_options): - MochitestBase.__init__(self, logger_options) + super(Mochitest, self).__init__(logger_options) # Max time in seconds to wait for server startup before tests will fail -- if # this seems big, it's mostly for debug machines where cold startup # (particularly after a build) takes forever. self.SERVER_STARTUP_TIMEOUT = 180 if mozinfo.info.get('debug') else 90 # metro browser sub process id self.browserProcessId = None @@ -1518,16 +1251,20 @@ class MochitestDesktop(MochitestBase): # Create variables to count the number of passes, fails, todos. self.countpass = 0 self.countfail = 0 self.counttodo = 0 self.expectedError = {} self.result = {} + def environment(self, **kwargs): + kwargs['log'] = self.log + return test_environment(**kwargs) + def extraPrefs(self, extraPrefs): """interpolate extra preferences from option strings""" try: return dict(parseKeyValue(extraPrefs, context='--setpref=')) except KeyValueParseError as e: print str(e) sys.exit(1) @@ -1725,16 +1462,85 @@ class MochitestDesktop(MochitestBase): if os.path.isdir(os.path.join(parent, sub))] if not gmp_paths: # This is fatal for desktop environments. raise EnvironmentError('Could not find test gmp plugins') return os.pathsep.join(gmp_paths) + def buildBrowserEnv(self, options, debugger=False, env=None): + """build the environment variables for the specific test and operating system""" + if mozinfo.info["asan"]: + lsanPath = SCRIPT_DIR + else: + lsanPath = None + + browserEnv = self.environment( + xrePath=options.xrePath, + env=env, + debugger=debugger, + dmdPath=options.dmdPath, + lsanPath=lsanPath) + + # These variables are necessary for correct application startup; change + # via the commandline at your own risk. + browserEnv["XPCOM_DEBUG_BREAK"] = "stack" + + # When creating child processes on Windows pre-Vista (e.g. Windows XP) we + # don't normally inherit stdout/err handles, because you can only do it by + # inheriting all other inheritable handles as well. + # We need to inherit them for plain mochitests for test logging purposes, so + # we do so on the basis of a specific environment variable. + if self.getTestFlavor(options) == "mochitest": + browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"] = "1" + + # interpolate environment passed with options + try: + browserEnv.update( + dict( + parseKeyValue( + options.environment, + context='--setenv'))) + except KeyValueParseError as e: + self.log.error(str(e)) + return None + + browserEnv["XPCOM_MEM_BLOAT_LOG"] = self.leak_report_file + + try: + gmp_path = self.getGMPPluginPath(options) + if gmp_path is not None: + browserEnv["MOZ_GMP_PATH"] = gmp_path + except EnvironmentError: + self.log.error('Could not find path to gmp-fake plugin!') + return None + + if options.fatalAssertions: + browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort" + + # Produce an NSPR log, is setup (see NSPR_LOG_MODULES global at the top of + # this script). + self.nsprLogs = NSPR_LOG_MODULES and "MOZ_UPLOAD_DIR" in os.environ + if self.nsprLogs: + browserEnv["NSPR_LOG_MODULES"] = NSPR_LOG_MODULES + + browserEnv["NSPR_LOG_FILE"] = "%s/nspr.log" % tempfile.gettempdir() + browserEnv["GECKO_SEPARATE_NSPR_LOGS"] = "1" + + if debugger and not options.slowscript: + browserEnv["JS_DISABLE_SLOW_SCRIPT_SIGNALS"] = "1" + + # For e10s, our tests default to suppressing the "unsafe CPOW usage" + # warnings that can plague test logs. + if not options.enableCPOWWarnings: + browserEnv["DISABLE_UNSAFE_CPOW_WARNINGS"] = "1" + + return browserEnv + def cleanup(self, options): """ remove temporary files and profile """ if hasattr(self, 'manifest') and self.manifest is not None: os.remove(self.manifest) if hasattr(self, 'profile'): del self.profile if options.pidFile != "": try: @@ -1862,16 +1668,18 @@ class MochitestDesktop(MochitestBase): interactive = debuggerInfo.interactive debug_args = [debuggerInfo.path] + debuggerInfo.args # Set up Valgrind arguments. if valgrindPath: interactive = False valgrindArgs_split = ([] if valgrindArgs is None else valgrindArgs.split()) + valgrindSuppFiles_split = ([] if valgrindSuppFiles is None + else valgrindSuppFiles.split(",")) valgrindSuppFiles_final = [] if valgrindSuppFiles is not None: valgrindSuppFiles_final = ["--suppressions=" + path for path in valgrindSuppFiles.split(",")] debug_args = ([valgrindPath] + mozdebug.get_default_valgrind_args() + valgrindArgs_split @@ -2064,16 +1872,143 @@ class MochitestDesktop(MochitestBase): for p in paths: abspath = os.path.abspath(os.path.join(self.oldcwd, p)) if abspath.startswith(self.testRootAbs): norm_paths.append(os.path.relpath(abspath, self.testRootAbs)) else: norm_paths.append(p) return norm_paths + def getActiveTests(self, options, disabled=True): + """ + This method is used to parse the manifest and return active filtered tests. + """ + if self._active_tests: + return self._active_tests + + manifest = self.getTestManifest(options) + if manifest: + if options.extra_mozinfo_json: + mozinfo.update(options.extra_mozinfo_json) + info = mozinfo.info + + # Bug 1089034 - imptest failure expectations are encoded as + # test manifests, even though they aren't tests. This gross + # hack causes several problems in automation including + # throwing off the chunking numbers. Remove them manually + # until bug 1089034 is fixed. + def remove_imptest_failure_expectations(tests, values): + return (t for t in tests + if 'imptests/failures' not in t['path']) + + filters = [ + remove_imptest_failure_expectations, + subsuite(options.subsuite), + ] + + if options.test_tags: + filters.append(tags(options.test_tags)) + + if options.test_paths: + options.test_paths = self.normalize_paths(options.test_paths) + filters.append(pathprefix(options.test_paths)) + + # Add chunking filters if specified + if options.totalChunks: + if options.chunkByRuntime: + runtime_file = self.resolve_runtime_file(options) + if not os.path.exists(runtime_file): + self.log.warning("runtime file %s not found; defaulting to chunk-by-dir" % + runtime_file) + options.chunkByRuntime = None + flavor = self.getTestFlavor(options) + if flavor in ('browser-chrome', 'devtools-chrome'): + # these values match current mozharness configs + options.chunkbyDir = 5 + else: + options.chunkByDir = 4 + + if options.chunkByDir: + filters.append(chunk_by_dir(options.thisChunk, + options.totalChunks, + options.chunkByDir)) + elif options.chunkByRuntime: + with open(runtime_file, 'r') as f: + runtime_data = json.loads(f.read()) + runtimes = runtime_data['runtimes'] + default = runtime_data['excluded_test_average'] + filters.append( + chunk_by_runtime(options.thisChunk, + options.totalChunks, + runtimes, + default_runtime=default)) + else: + filters.append(chunk_by_slice(options.thisChunk, + options.totalChunks)) + + tests = manifest.active_tests( + exists=False, disabled=disabled, filters=filters, **info) + + if len(tests) == 0: + self.log.error("no tests to run using specified " + "combination of filters: {}".format( + manifest.fmt_filters())) + + paths = [] + for test in tests: + if len(tests) == 1 and 'disabled' in test: + del test['disabled'] + + pathAbs = os.path.abspath(test['path']) + assert pathAbs.startswith(self.testRootAbs) + tp = pathAbs[len(self.testRootAbs):].replace('\\', '/').strip('/') + + if not self.isTest(options, tp): + self.log.warning( + 'Warning: %s from manifest %s is not a valid test' % + (test['name'], test['manifest'])) + continue + + testob = {'path': tp} + if 'disabled' in test: + testob['disabled'] = test['disabled'] + if 'expected' in test: + testob['expected'] = test['expected'] + paths.append(testob) + + def path_sort(ob1, ob2): + path1 = ob1['path'].split('/') + path2 = ob2['path'].split('/') + return cmp(path1, path2) + + paths.sort(path_sort) + self._active_tests = paths + if options.dump_tests: + options.dump_tests = os.path.expanduser(options.dump_tests) + assert os.path.exists(os.path.dirname(options.dump_tests)) + with open(options.dump_tests, 'w') as dumpFile: + dumpFile.write(json.dumps({'active_tests': self._active_tests})) + + self.log.info("Dumping active_tests to %s file." % options.dump_tests) + sys.exit() + + return self._active_tests + + def logPreamble(self, tests): + """Logs a suite_start message and test_start/test_end at the beginning of a run. + """ + self.log.suite_start([t['path'] for t in tests]) + for test in tests: + if 'disabled' in test: + self.log.test_start(test['path']) + self.log.test_end( + test['path'], + 'SKIP', + message=test['disabled']) + def getTestsToRun(self, options): """ This method makes a list of tests that are to be run. Required mainly for --bisect-chunk. """ tests = self.getActiveTests(options) self.logPreamble(tests) testsToRun = [] @@ -2116,16 +2051,39 @@ class MochitestDesktop(MochitestBase): # We need to print the summary only if options.bisectChunk has a value. # Also we need to make sure that we do not print the summary in between # running tests via --run-by-dir. if options.bisectChunk and options.bisectChunk in self.result: bisect.print_summary() return result + def killNamedOrphans(self, pname): + """ Kill orphan processes matching the given command name """ + self.log.info("Checking for orphan %s processes..." % pname) + def _psInfo(line): + if pname in line: + self.log.info(line) + process = mozprocess.ProcessHandler(['ps', '-f'], + processOutputLine=_psInfo) + process.run() + process.wait() + + def _psKill(line): + parts = line.split() + if len(parts) == 3 and parts[0].isdigit(): + pid = int(parts[0]) + if parts[2] == pname and parts[1] == '1': + self.log.info("killing %s orphan with pid %d" % (pname, pid)) + killPid(pid, self.log) + process = mozprocess.ProcessHandler(['ps', '-o', 'pid,ppid,comm'], + processOutputLine=_psKill) + process.run() + process.wait() + def runTests(self, options, onLaunch=None): """ Prepare, configure, run tests and cleanup """ self.setTestRoot(options) # Despite our efforts to clean up servers started by this script, in practice # we still see infrequent cases where a process is orphaned and interferes # with future tests, typically because the old server is keeping the port in use. @@ -2237,16 +2195,17 @@ class MochitestDesktop(MochitestBase): self.leak_report_file = os.path.join( options.profilePath, "runtests_leaks.log") self.browserEnv = self.buildBrowserEnv( options, debuggerInfo is not None) + # If there are any Mulet-specific tests doing remote network access, # we will not be aware since we are explicitely allowing this, as for # B2G # # In addition, the push subsuite directly accesses the production # push service. if 'MOZ_DISABLE_NONLOCAL_CONNECTIONS' in self.browserEnv: if mozinfo.info.get('buildapp') == 'mulet' or options.subsuite == 'push': @@ -2550,16 +2509,62 @@ class MochitestDesktop(MochitestBase): self.lsanLeaks.log(message['message']) return message def trackShutdownLeaks(self, message): if self.shutdownLeaks: self.shutdownLeaks.log(message) return message + def makeTestConfig(self, options): + "Creates a test configuration file for customizing test execution." + options.logFile = options.logFile.replace("\\", "\\\\") + + if "MOZ_HIDE_RESULTS_TABLE" in os.environ and os.environ[ + "MOZ_HIDE_RESULTS_TABLE"] == "1": + options.hideResultsTable = True + + # strip certain unnecessary items to avoid serialization errors in json.dumps() + d = dict((k, v) for k, v in options.__dict__.items() if (v is None) or + isinstance(v,(basestring,numbers.Number))) + d['testRoot'] = self.testRoot + if not options.keep_open: + d['closeWhenDone'] = '1' + content = json.dumps(d) + + with open(os.path.join(options.profilePath, "testConfig.js"), "w") as config: + config.write(content) + + def getTestManifest(self, options): + if isinstance(options.manifestFile, TestManifest): + manifest = options.manifestFile + elif options.manifestFile and os.path.isfile(options.manifestFile): + manifestFileAbs = os.path.abspath(options.manifestFile) + assert manifestFileAbs.startswith(SCRIPT_DIR) + manifest = TestManifest([options.manifestFile], strict=False) + elif options.manifestFile and os.path.isfile(os.path.join(SCRIPT_DIR, options.manifestFile)): + manifestFileAbs = os.path.abspath( + os.path.join( + SCRIPT_DIR, + options.manifestFile)) + assert manifestFileAbs.startswith(SCRIPT_DIR) + manifest = TestManifest([manifestFileAbs], strict=False) + else: + masterName = self.getTestFlavor(options) + '.ini' + masterPath = os.path.join(SCRIPT_DIR, self.testRoot, masterName) + + if os.path.exists(masterPath): + manifest = TestManifest([masterPath], strict=False) + else: + self._log.warning( + 'TestManifest masterPath %s does not exist' % + masterPath) + + return manifest + def getDirectories(self, options): """ Make the list of directories by parsing manifests """ tests = self.getActiveTests(options) dirlist = [] for test in tests: if 'disabled' in test: @@ -2570,18 +2575,18 @@ class MochitestDesktop(MochitestBase): dirlist.append(rootdir) return dirlist def run_test_harness(options): logger_options = { key: value for key, value in vars(options).iteritems() - if key.startswith('log') or key == 'valgrind'} - runner = MochitestDesktop(logger_options) + if key.startswith('log') or key == 'valgrind' } + runner = Mochitest(logger_options) options.runByDir = False if runner.getTestFlavor(options) == 'mochitest': options.runByDir = True if runner.getTestFlavor(options) == 'browser-chrome': options.runByDir = True
--- a/testing/mochitest/runtestsb2g.py +++ b/testing/mochitest/runtestsb2g.py @@ -1,77 +1,84 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. import json import os import posixpath +import shutil import sys import tempfile +import threading import traceback here = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, here) -from runtests import MochitestBase +from runtests import Mochitest +from runtests import MochitestUtilsMixin from mochitest_options import MochitestArgumentParser from marionette import Marionette from mozprofile import Profile, Preferences from mozrunner.utils import get_stack_fixer_function import mozinfo import mozleak -class MochitestB2G(MochitestBase): - """ - Mochitest class for b2g emulators and devices. - """ +class B2GMochitest(MochitestUtilsMixin): marionette = None - remote_log = None def __init__(self, marionette_args, logger_options, - profile_data_dir, - local_binary_dir, - locations=os.path.join(here, 'server-locations.txt'), out_of_process=True, - remote_test_root=None, - remote_log_file=None): - MochitestBase.__init__(self, logger_options) + profile_data_dir=None, + locations=os.path.join(here, 'server-locations.txt')): + super(B2GMochitest, self).__init__(logger_options) self.marionette_args = marionette_args self.out_of_process = out_of_process self.locations_file = locations self.preferences = [] self.webapps = None self.test_script = os.path.join(here, 'b2g_start_script.js') self.test_script_args = [self.out_of_process] self.product = 'b2g' self.remote_chrome_test_dir = None - self.local_log = None - self.local_binary_dir = local_binary_dir - self.preferences = [ - os.path.join( - profile_data_dir, - f) for f in os.listdir(profile_data_dir) if f.startswith('pref')] - self.webapps = [ - os.path.join( - profile_data_dir, - f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')] + if profile_data_dir: + self.preferences = [ + os.path.join( + profile_data_dir, + f) for f in os.listdir(profile_data_dir) if f.startswith('pref')] + self.webapps = [ + os.path.join( + profile_data_dir, + f) for f in os.listdir(profile_data_dir) if f.startswith('webapp')] # mozinfo is populated by the parent class if mozinfo.info['debug']: self.SERVER_STARTUP_TIMEOUT = 180 else: self.SERVER_STARTUP_TIMEOUT = 90 + def setup_common_options(self, options): + test_url = self.buildTestPath(options) + # For B2G emulators buildURLOptions has been called + # without calling buildTestPath first and that + # causes manifestFile not to be set + if not "manifestFile=tests.json" in self.urlOpts: + self.urlOpts.append("manifestFile=%s" % options.manifestFile) + + if len(self.urlOpts) > 0: + test_url += "?" + "&".join(self.urlOpts) + self.test_script_args.append(test_url) + def buildTestPath(self, options, testsToFilter=None): if options.manifestFile != 'tests.json': - MochitestBase.buildTestPath(self, options, testsToFilter, disabled=False) + super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False) return self.buildTestURL(options) def build_profile(self, options): # preferences prefs = {} for path in self.preferences: prefs.update(Preferences.read_prefs(path)) @@ -95,17 +102,21 @@ class MochitestB2G(MochitestBase): kwargs = { 'addons': self.getExtensionsToInstall(options), 'apps': self.webapps, 'locations': self.locations_file, 'preferences': prefs, 'proxy': {"remote": options.webServer} } - self.profile = Profile(**kwargs) + if options.profile: + self.profile = Profile.clone(options.profile, **kwargs) + else: + self.profile = Profile(**kwargs) + options.profilePath = self.profile.profile # TODO bug 839108 - mozprofile should probably handle this manifest = self.addChromeToProfile(options) self.copyExtraFilesToProfile(options) return manifest def run_tests(self, options): """ Prepare, configure, run tests and cleanup """ @@ -228,17 +239,17 @@ class MochitestB2G(MochitestBase): testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver(); testUtils.specialPowersObserver.init(); } """) if options.chrome: self.app_ctx.dm.removeDir(self.remote_chrome_test_dir) self.app_ctx.dm.mkDir(self.remote_chrome_test_dir) - local = MochitestBase.getChromeTestDir(self, options) + local = super(B2GMochitest, self).getChromeTestDir(options) local = os.path.join(local, "chrome") remote = self.remote_chrome_test_dir self.log.info( "pushing %s to %s on device..." % (local, remote)) self.app_ctx.dm.pushDir(local, remote) if os.path.isfile(self.test_script): @@ -303,16 +314,37 @@ class MochitestB2G(MochitestBase): # writing the dummy. if hasattr(self, 'app_ctx'): self.remote_chrome_test_dir = posixpath.join( self.app_ctx.remote_test_root, 'chrome') return self.remote_chrome_test_dir return 'dummy-chrome-test-dir' + +class B2GDeviceMochitest(B2GMochitest, Mochitest): + remote_log = None + + def __init__( + self, + marionette_args, + logger_options, + profile_data_dir, + local_binary_dir, + remote_test_root=None, + remote_log_file=None): + B2GMochitest.__init__( + self, + marionette_args, + logger_options, + out_of_process=True, + profile_data_dir=profile_data_dir) + self.local_log = None + self.local_binary_dir = local_binary_dir + def cleanup(self, manifest, options): if self.local_log: self.app_ctx.dm.getFile(self.remote_log, self.local_log) self.app_ctx.dm.removeFile(self.remote_log) if options.pidFile != "": try: os.remove(options.pidFile) @@ -333,46 +365,92 @@ class MochitestB2G(MochitestBase): """ Create the servers on the host and start them up """ savedXre = options.xrePath savedUtility = options.utilityPath savedProfie = options.profilePath options.xrePath = self.local_binary_dir options.utilityPath = self.local_binary_dir options.profilePath = tempfile.mkdtemp() - MochitestBase.startServers(self, options, debuggerInfo) + MochitestUtilsMixin.startServers(self, options, debuggerInfo) options.xrePath = savedXre options.utilityPath = savedUtility options.profilePath = savedProfie def buildURLOptions(self, options, env): self.local_log = options.logFile options.logFile = self.remote_log options.profilePath = self.profile.profile - MochitestBase.buildURLOptions(self, options, env) - - test_url = self.buildTestPath(options) + super(B2GDeviceMochitest, self).buildURLOptions(options, env) - # For B2G emulators buildURLOptions has been called - # without calling buildTestPath first and that - # causes manifestFile not to be set - if "manifestFile=tests.json" not in self.urlOpts: - self.urlOpts.append("manifestFile=%s" % options.manifestFile) - - if len(self.urlOpts) > 0: - test_url += "?" + "&".join(self.urlOpts) - self.test_script_args.append(test_url) - + self.setup_common_options(options) options.profilePath = self.app_ctx.remote_profile options.logFile = self.local_log -def run_test_harness(options): +class B2GDesktopMochitest(B2GMochitest, Mochitest): + + def __init__(self, marionette_args, logger_options, profile_data_dir): + B2GMochitest.__init__( + self, + marionette_args, + logger_options, + out_of_process=False, + profile_data_dir=profile_data_dir) + Mochitest.__init__(self, logger_options) + self.certdbNew = True + + def runMarionetteScript(self, marionette, test_script, test_script_args): + assert(marionette.wait_for_port()) + marionette.start_session() + marionette.set_context(marionette.CONTEXT_CHROME) + + if os.path.isfile(test_script): + f = open(test_script, 'r') + test_script = f.read() + f.close() + self.marionette.execute_script(test_script, + script_args=test_script_args) + + def startTests(self): + # This is run in a separate thread because otherwise, the app's + # stdout buffer gets filled (which gets drained only after this + # function returns, by waitForFinish), which causes the app to hang. + self.marionette = Marionette(**self.marionette_args) + thread = threading.Thread(target=self.runMarionetteScript, + args=(self.marionette, + self.test_script, + self.test_script_args)) + thread.start() + + def buildURLOptions(self, options, env): + super(B2GDesktopMochitest, self).buildURLOptions(options, env) + + self.setup_common_options(options) + + # Copy the extensions to the B2G bundles dir. + extensionDir = os.path.join( + options.profilePath, + 'extensions', + 'staged') + bundlesDir = os.path.join(os.path.dirname(options.app), + 'distribution', 'bundles') + + for filename in os.listdir(extensionDir): + shutil.rmtree(os.path.join(bundlesDir, filename), True) + shutil.copytree(os.path.join(extensionDir, filename), + os.path.join(bundlesDir, filename)) + + def buildProfile(self, options): + return self.build_profile(options) + + +def run_remote_mochitests(options): # create our Marionette instance marionette_args = { 'adb_path': options.adbPath, 'emulator': options.emulator, 'no_window': options.noWindow, 'logdir': options.logdir, 'busybox': options.busybox, 'symbols_path': options.symbolsPath, @@ -383,17 +461,17 @@ def run_test_harness(options): host, port = options.marionette.split(':') marionette_args['host'] = host marionette_args['port'] = int(port) if (options is None): print "ERROR: Invalid options specified, use --help for a list of valid options" sys.exit(1) - mochitest = MochitestB2G( + mochitest = B2GDeviceMochitest( marionette_args, options, options.profile_data_dir, options.xrePath, remote_log_file=options.remoteLogFile) if (options is None): sys.exit(1) @@ -412,15 +490,50 @@ def run_test_harness(options): pass retVal = 1 mochitest.message_logger.finish() return retVal +def run_desktop_mochitests(options): + # create our Marionette instance + marionette_args = {} + if options.marionette: + host, port = options.marionette.split(':') + marionette_args['host'] = host + marionette_args['port'] = int(port) + + # add a -bin suffix if b2g-bin exists, but just b2g was specified + if options.app[-4:] != '-bin': + if os.path.isfile("%s-bin" % options.app): + options.app = "%s-bin" % options.app + + mochitest = B2GDesktopMochitest( + marionette_args, + options, + options.profile_data_dir) + if options is None: + sys.exit(1) + + if options.desktop and not options.profile: + raise Exception("must specify --profile when specifying --desktop") + + options.browserArgs += ['-marionette'] + options.runByDir = False + retVal = mochitest.runTests(options, onLaunch=mochitest.startTests) + mochitest.message_logger.finish() + + return retVal + + def main(): parser = MochitestArgumentParser(app='b2g') options = parser.parse_args() - return run_test_harness(options) + + if options.desktop: + return run_desktop_mochitests(options) + else: + return run_remote_mochitests(options) if __name__ == "__main__": sys.exit(main())
--- a/testing/mochitest/runtestsremote.py +++ b/testing/mochitest/runtestsremote.py @@ -8,34 +8,33 @@ import traceback sys.path.insert( 0, os.path.abspath( os.path.realpath( os.path.dirname(__file__)))) from automation import Automation from remoteautomation import RemoteAutomation, fennecLogcatFilters -from runtests import MochitestDesktop, MessageLogger +from runtests import Mochitest, MessageLogger from mochitest_options import MochitestArgumentParser import devicemanager import mozinfo SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__))) -# TODO inherit from MochitestBase instead -class MochiRemote(MochitestDesktop): +class MochiRemote(Mochitest): _automation = None _dm = None localProfile = None logMessages = [] def __init__(self, automation, devmgr, options): - MochitestDesktop.__init__(self, options) + Mochitest.__init__(self, options) self._automation = automation self._dm = devmgr self.environment = self._automation.environment self.remoteProfile = options.remoteTestRoot + "/profile" self._automation.setRemoteProfile(self.remoteProfile) self.remoteLog = options.remoteLogFile self.localLog = options.logFile @@ -59,17 +58,17 @@ class MochiRemote(MochitestDesktop): self.log.warning( "Unable to retrieve log file (%s) from remote device" % self.remoteLog) self._dm.removeDir(self.remoteProfile) self._dm.removeDir(self.remoteChromeTestDir) blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None) if blobberUploadDir: self._dm.getDirectory(self.remoteNSPR, blobberUploadDir) - MochitestDesktop.cleanup(self, options) + Mochitest.cleanup(self, options) def findPath(self, paths, filename=None): for path in paths: p = path if filename: p = os.path.join(p, filename) if os.path.exists(self.getFullPath(p)): return path @@ -151,26 +150,26 @@ class MochiRemote(MochitestDesktop): options.profilePath = remoteProfilePath return fixup def startServers(self, options, debuggerInfo): """ Create the servers on the host and start them up """ restoreRemotePaths = self.switchToLocalPaths(options) # ignoreSSLTunnelExts is a workaround for bug 1109310 - MochitestDesktop.startServers( + Mochitest.startServers( self, options, debuggerInfo, ignoreSSLTunnelExts=True) restoreRemotePaths() def buildProfile(self, options): restoreRemotePaths = self.switchToLocalPaths(options) - manifest = MochitestDesktop.buildProfile(self, options) + manifest = Mochitest.buildProfile(self, options) self.localProfile = options.profilePath self._dm.removeDir(self.remoteProfile) try: self._dm.pushDir(options.profilePath, self.remoteProfile) except devicemanager.DMError: self.log.error( "Automation Error: Unable to copy profile to device.") @@ -181,17 +180,17 @@ class MochiRemote(MochitestDesktop): return manifest def buildURLOptions(self, options, env): self.localLog = options.logFile options.logFile = self.remoteLog options.fileLevel = 'INFO' options.profilePath = self.localProfile env["MOZ_HIDE_RESULTS_TABLE"] = "1" - retVal = MochitestDesktop.buildURLOptions(self, options, env) + retVal = Mochitest.buildURLOptions(self, options, env) # we really need testConfig.js (for browser chrome) try: self._dm.pushDir(options.profilePath, self.remoteProfile) except devicemanager.DMError: self.log.error( "Automation Error: Unable to copy profile to device.") raise @@ -235,26 +234,26 @@ class MochiRemote(MochitestDesktop): except devicemanager.DMError: self.log.warning("Error getting device information") def getGMPPluginPath(self, options): # TODO: bug 1149374 return None def buildBrowserEnv(self, options, debugger=False): - browserEnv = MochitestDesktop.buildBrowserEnv( + browserEnv = Mochitest.buildBrowserEnv( self, options, debugger=debugger) # remove desktop environment not used on device if "MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA" in browserEnv: del browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"] if "XPCOM_MEM_BLOAT_LOG" in browserEnv: del browserEnv["XPCOM_MEM_BLOAT_LOG"] - # override nsprLogs to avoid processing in MochitestDesktop base class + # override nsprLogs to avoid processing in Mochitest base class self.nsprLogs = None browserEnv["NSPR_LOG_FILE"] = os.path.join( self.remoteNSPR, self.nsprLogName) return browserEnv def runApp(self, *args, **kwargs): """front-end automation.py's `runApp` functionality until FennecRunner is written"""