mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-14 22:05:44 +00:00
Bug 1225903 - Drop support for b2g desktop in mochitest, r=jgriffin
Mochitests on b2g desktop are no longer being run on any trunk branches, including b2g-inbound. Dropping support for it significantly reduces complexity in the mochitest harness. --HG-- extra : commitid : jAe5IJxAQp extra : rebase_source : 5f163aea70fb99a95667fdafeb7b3361bed1f82d
This commit is contained in:
parent
161662dac8
commit
629335ccca
@ -30,30 +30,6 @@ import mozpack.path as mozpath
|
|||||||
here = os.path.abspath(os.path.dirname(__file__))
|
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 = '''
|
ENG_BUILD_REQUIRED = '''
|
||||||
The mochitest command requires an engineering build. It may be the case that
|
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=user or PRODUCTION=1 were set. Try re-building with VARIANT=eng:
|
||||||
@ -99,7 +75,7 @@ ALL_FLAVORS = {
|
|||||||
'mochitest': {
|
'mochitest': {
|
||||||
'suite': 'plain',
|
'suite': 'plain',
|
||||||
'aliases': ('plain', 'mochitest'),
|
'aliases': ('plain', 'mochitest'),
|
||||||
'enabled_apps': ('firefox', 'b2g', 'android', 'mulet', 'b2g_desktop'),
|
'enabled_apps': ('firefox', 'b2g', 'android', 'mulet'),
|
||||||
},
|
},
|
||||||
'chrome': {
|
'chrome': {
|
||||||
'suite': 'chrome',
|
'suite': 'chrome',
|
||||||
@ -159,7 +135,7 @@ ALL_FLAVORS = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
SUPPORTED_APPS = ['firefox', 'b2g', 'android', 'mulet', 'b2g_desktop']
|
SUPPORTED_APPS = ['firefox', 'b2g', 'android', 'mulet']
|
||||||
SUPPORTED_FLAVORS = list(chain.from_iterable([f['aliases'] for f in ALL_FLAVORS.values()]))
|
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()])
|
CANONICAL_FLAVORS = sorted([f['aliases'][0] for f in ALL_FLAVORS.values()])
|
||||||
|
|
||||||
@ -236,17 +212,7 @@ class MochitestRunner(MozbuildObject):
|
|||||||
|
|
||||||
def run_b2g_test(self, context, tests=None, suite='mochitest', **kwargs):
|
def run_b2g_test(self, context, tests=None, suite='mochitest', **kwargs):
|
||||||
"""Runs a b2g mochitest."""
|
"""Runs a b2g mochitest."""
|
||||||
if kwargs.get('desktop'):
|
if context.target_out:
|
||||||
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')
|
host_webapps_dir = os.path.join(context.target_out, 'data', 'local', 'webapps')
|
||||||
if not os.path.isdir(os.path.join(
|
if not os.path.isdir(os.path.join(
|
||||||
host_webapps_dir, 'test-container.gaiamobile.org')):
|
host_webapps_dir, 'test-container.gaiamobile.org')):
|
||||||
@ -277,10 +243,7 @@ class MochitestRunner(MozbuildObject):
|
|||||||
manifest.tests.extend(tests)
|
manifest.tests.extend(tests)
|
||||||
options.manifestFile = manifest
|
options.manifestFile = manifest
|
||||||
|
|
||||||
if options.desktop:
|
return mochitest.run_test_harness(options)
|
||||||
return mochitest.run_desktop_mochitests(options)
|
|
||||||
|
|
||||||
return mochitest.run_remote_mochitests(options)
|
|
||||||
|
|
||||||
def run_desktop_test(self, context, tests=None, suite=None, **kwargs):
|
def run_desktop_test(self, context, tests=None, suite=None, **kwargs):
|
||||||
"""Runs a mochitest.
|
"""Runs a mochitest.
|
||||||
@ -562,7 +525,7 @@ class MachCommands(MachCommandBase):
|
|||||||
buildapp, '\n'.join(sorted(msg))))
|
buildapp, '\n'.join(sorted(msg))))
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if buildapp in ('b2g', 'b2g_desktop'):
|
if buildapp in ('b2g',):
|
||||||
run_mochitest = mochitest.run_b2g_test
|
run_mochitest = mochitest.run_b2g_test
|
||||||
elif buildapp == 'android':
|
elif buildapp == 'android':
|
||||||
run_mochitest = mochitest.run_android_test
|
run_mochitest = mochitest.run_android_test
|
||||||
|
@ -596,7 +596,7 @@ class MochitestArguments(ArgumentContainer):
|
|||||||
|
|
||||||
if options.extra_mozinfo_json:
|
if options.extra_mozinfo_json:
|
||||||
if not os.path.isfile(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)
|
||||||
|
|
||||||
options.extra_mozinfo_json = json.load(open(options.extra_mozinfo_json))
|
options.extra_mozinfo_json = json.load(open(options.extra_mozinfo_json))
|
||||||
@ -794,12 +794,6 @@ class B2GArguments(ArgumentContainer):
|
|||||||
"help": "Path to B2G repo or QEMU directory.",
|
"help": "Path to B2G repo or QEMU directory.",
|
||||||
"suppress": True,
|
"suppress": True,
|
||||||
}],
|
}],
|
||||||
[["--desktop"],
|
|
||||||
{"action": "store_true",
|
|
||||||
"default": False,
|
|
||||||
"help": "Run the tests on a B2G desktop build.",
|
|
||||||
"suppress": True,
|
|
||||||
}],
|
|
||||||
[["--marionette"],
|
[["--marionette"],
|
||||||
{"default": None,
|
{"default": None,
|
||||||
"help": "host:port to use when connecting to Marionette",
|
"help": "host:port to use when connecting to Marionette",
|
||||||
@ -873,11 +867,6 @@ class B2GArguments(ArgumentContainer):
|
|||||||
"prior to test.",
|
"prior to test.",
|
||||||
"suppress": True,
|
"suppress": True,
|
||||||
}],
|
}],
|
||||||
[["--profile"],
|
|
||||||
{"dest": "profile",
|
|
||||||
"default": None,
|
|
||||||
"help": "For desktop testing, the path to the gaia profile to use.",
|
|
||||||
}],
|
|
||||||
[["--logdir"],
|
[["--logdir"],
|
||||||
{"dest": "logdir",
|
{"dest": "logdir",
|
||||||
"default": None,
|
"default": None,
|
||||||
@ -909,20 +898,6 @@ class B2GArguments(ArgumentContainer):
|
|||||||
def validate(self, parser, options, context):
|
def validate(self, parser, options, context):
|
||||||
"""Validate b2g options."""
|
"""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 options.remoteWebServer is None:
|
||||||
if os.name != "nt":
|
if os.name != "nt":
|
||||||
options.remoteWebServer = moznetwork.get_ip()
|
options.remoteWebServer = moznetwork.get_ip()
|
||||||
@ -1193,7 +1168,7 @@ class MochitestArgumentParser(ArgumentParser):
|
|||||||
if not self.app and build_obj:
|
if not self.app and build_obj:
|
||||||
if conditions.is_android(build_obj):
|
if conditions.is_android(build_obj):
|
||||||
self.app = 'android'
|
self.app = 'android'
|
||||||
elif conditions.is_b2g(build_obj) or conditions.is_b2g_desktop(build_obj):
|
elif conditions.is_b2g(build_obj):
|
||||||
self.app = 'b2g'
|
self.app = 'b2g'
|
||||||
if not self.app:
|
if not self.app:
|
||||||
# platform can't be determined and app wasn't specified explicitly,
|
# platform can't be determined and app wasn't specified explicitly,
|
||||||
|
@ -16,7 +16,7 @@ sys.path.insert(
|
|||||||
|
|
||||||
from automation import Automation
|
from automation import Automation
|
||||||
from remoteautomation import RemoteAutomation, fennecLogcatFilters
|
from remoteautomation import RemoteAutomation, fennecLogcatFilters
|
||||||
from runtests import Mochitest, MessageLogger
|
from runtests import MochitestDesktop, MessageLogger
|
||||||
from mochitest_options import MochitestArgumentParser
|
from mochitest_options import MochitestArgumentParser
|
||||||
|
|
||||||
from manifestparser import TestManifest
|
from manifestparser import TestManifest
|
||||||
@ -27,7 +27,8 @@ import mozinfo
|
|||||||
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
class RobocopTestRunner(Mochitest):
|
# TODO inherit from MochitestBase instead
|
||||||
|
class RobocopTestRunner(MochitestDesktop):
|
||||||
"""
|
"""
|
||||||
A test harness for Robocop. Robocop tests are UI tests for Firefox for Android,
|
A test harness for Robocop. Robocop tests are UI tests for Firefox for Android,
|
||||||
based on the Robotium test framework. This harness leverages some functionality
|
based on the Robotium test framework. This harness leverages some functionality
|
||||||
@ -42,7 +43,7 @@ class RobocopTestRunner(Mochitest):
|
|||||||
"""
|
"""
|
||||||
Simple one-time initialization.
|
Simple one-time initialization.
|
||||||
"""
|
"""
|
||||||
Mochitest.__init__(self, options)
|
MochitestDesktop.__init__(self, options)
|
||||||
|
|
||||||
self.auto = automation
|
self.auto = automation
|
||||||
self.dm = devmgr
|
self.dm = devmgr
|
||||||
@ -131,7 +132,7 @@ class RobocopTestRunner(Mochitest):
|
|||||||
blobberUploadDir)
|
blobberUploadDir)
|
||||||
self.dm.getDirectory(self.remoteNSPR, blobberUploadDir)
|
self.dm.getDirectory(self.remoteNSPR, blobberUploadDir)
|
||||||
self.dm.getDirectory(self.remoteScreenshots, blobberUploadDir)
|
self.dm.getDirectory(self.remoteScreenshots, blobberUploadDir)
|
||||||
Mochitest.cleanup(self, self.options)
|
MochitestDesktop.cleanup(self, self.options)
|
||||||
if self.localProfile:
|
if self.localProfile:
|
||||||
os.system("rm -Rf %s" % self.localProfile)
|
os.system("rm -Rf %s" % self.localProfile)
|
||||||
self.dm.removeDir(self.remoteProfile)
|
self.dm.removeDir(self.remoteProfile)
|
||||||
@ -225,7 +226,7 @@ class RobocopTestRunner(Mochitest):
|
|||||||
self.options.extraPrefs.append('browser.snippets.enabled=false')
|
self.options.extraPrefs.append('browser.snippets.enabled=false')
|
||||||
self.options.extraPrefs.append('browser.casting.enabled=true')
|
self.options.extraPrefs.append('browser.casting.enabled=true')
|
||||||
self.options.extraPrefs.append('extensions.autoupdate.enabled=false')
|
self.options.extraPrefs.append('extensions.autoupdate.enabled=false')
|
||||||
manifest = Mochitest.buildProfile(self, self.options)
|
manifest = MochitestDesktop.buildProfile(self, self.options)
|
||||||
self.localProfile = self.options.profilePath
|
self.localProfile = self.options.profilePath
|
||||||
self.log.debug("Profile created at %s" % self.localProfile)
|
self.log.debug("Profile created at %s" % self.localProfile)
|
||||||
# some files are not needed for robocop; save time by not pushing
|
# some files are not needed for robocop; save time by not pushing
|
||||||
|
@ -492,18 +492,11 @@ class WebSocketServer(object):
|
|||||||
self._process.kill()
|
self._process.kill()
|
||||||
|
|
||||||
|
|
||||||
class MochitestUtilsMixin(object):
|
class MochitestBase(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Class containing some utility functions common to both local and remote
|
Base mochitest class for both desktop and b2g.
|
||||||
mochitest runners
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 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()
|
oldcwd = os.getcwd()
|
||||||
jarDir = 'mochijar'
|
jarDir = 'mochijar'
|
||||||
|
|
||||||
@ -519,6 +512,7 @@ class MochitestUtilsMixin(object):
|
|||||||
self.server = None
|
self.server = None
|
||||||
self.wsserver = None
|
self.wsserver = None
|
||||||
self.sslTunnel = None
|
self.sslTunnel = None
|
||||||
|
self._active_tests = None
|
||||||
self._locations = None
|
self._locations = None
|
||||||
|
|
||||||
if self.log is None:
|
if self.log is None:
|
||||||
@ -530,7 +524,7 @@ class MochitestUtilsMixin(object):
|
|||||||
{
|
{
|
||||||
"tbpl": sys.stdout
|
"tbpl": sys.stdout
|
||||||
})
|
})
|
||||||
MochitestUtilsMixin.log = self.log
|
MochitestBase.log = self.log
|
||||||
|
|
||||||
self.message_logger = MessageLogger(logger=self.log)
|
self.message_logger = MessageLogger(logger=self.log)
|
||||||
|
|
||||||
@ -548,6 +542,10 @@ class MochitestUtilsMixin(object):
|
|||||||
|
|
||||||
mozinfo.find_and_update_from_json(*dirs)
|
mozinfo.find_and_update_from_json(*dirs)
|
||||||
|
|
||||||
|
def environment(self, **kwargs):
|
||||||
|
kwargs['log'] = self.log
|
||||||
|
return test_environment(**kwargs)
|
||||||
|
|
||||||
def getFullPath(self, path):
|
def getFullPath(self, path):
|
||||||
" Get an absolute path relative to self.oldcwd."
|
" Get an absolute path relative to self.oldcwd."
|
||||||
return os.path.normpath(
|
return os.path.normpath(
|
||||||
@ -994,6 +992,273 @@ overlay chrome://browser/content/browser.xul chrome://mochikit/content/jetpack-a
|
|||||||
extensions.append(os.path.join(SCRIPT_DIR, self.jarDir))
|
extensions.append(os.path.join(SCRIPT_DIR, self.jarDir))
|
||||||
return extensions
|
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:
|
class SSLTunnel:
|
||||||
|
|
||||||
@ -1225,8 +1490,10 @@ def parseKeyValue(strings, separator='=', context='key, value: '):
|
|||||||
return [string.split(separator, 1) for string in strings]
|
return [string.split(separator, 1) for string in strings]
|
||||||
|
|
||||||
|
|
||||||
class Mochitest(MochitestUtilsMixin):
|
class MochitestDesktop(MochitestBase):
|
||||||
_active_tests = None
|
"""
|
||||||
|
Mochitest class for desktop firefox and mulet.
|
||||||
|
"""
|
||||||
certdbNew = False
|
certdbNew = False
|
||||||
sslTunnel = None
|
sslTunnel = None
|
||||||
DEFAULT_TIMEOUT = 60.0
|
DEFAULT_TIMEOUT = 60.0
|
||||||
@ -1237,7 +1504,7 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
test_name = 'automation.py'
|
test_name = 'automation.py'
|
||||||
|
|
||||||
def __init__(self, logger_options):
|
def __init__(self, logger_options):
|
||||||
super(Mochitest, self).__init__(logger_options)
|
MochitestBase.__init__(self, logger_options)
|
||||||
|
|
||||||
# Max time in seconds to wait for server startup before tests will fail -- if
|
# 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
|
# this seems big, it's mostly for debug machines where cold startup
|
||||||
@ -1256,10 +1523,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
self.expectedError = {}
|
self.expectedError = {}
|
||||||
self.result = {}
|
self.result = {}
|
||||||
|
|
||||||
def environment(self, **kwargs):
|
|
||||||
kwargs['log'] = self.log
|
|
||||||
return test_environment(**kwargs)
|
|
||||||
|
|
||||||
def extraPrefs(self, extraPrefs):
|
def extraPrefs(self, extraPrefs):
|
||||||
"""interpolate extra preferences from option strings"""
|
"""interpolate extra preferences from option strings"""
|
||||||
|
|
||||||
@ -1467,75 +1730,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
|
|
||||||
return os.pathsep.join(gmp_paths)
|
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):
|
def cleanup(self, options):
|
||||||
""" remove temporary files and profile """
|
""" remove temporary files and profile """
|
||||||
if hasattr(self, 'manifest') and self.manifest is not None:
|
if hasattr(self, 'manifest') and self.manifest is not None:
|
||||||
@ -1673,8 +1867,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
interactive = False
|
interactive = False
|
||||||
valgrindArgs_split = ([] if valgrindArgs is None
|
valgrindArgs_split = ([] if valgrindArgs is None
|
||||||
else valgrindArgs.split())
|
else valgrindArgs.split())
|
||||||
valgrindSuppFiles_split = ([] if valgrindSuppFiles is None
|
|
||||||
else valgrindSuppFiles.split(","))
|
|
||||||
|
|
||||||
valgrindSuppFiles_final = []
|
valgrindSuppFiles_final = []
|
||||||
if valgrindSuppFiles is not None:
|
if valgrindSuppFiles is not None:
|
||||||
@ -1877,133 +2069,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
norm_paths.append(p)
|
norm_paths.append(p)
|
||||||
return norm_paths
|
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):
|
def getTestsToRun(self, options):
|
||||||
"""
|
"""
|
||||||
This method makes a list of tests that are to be run. Required mainly for --bisect-chunk.
|
This method makes a list of tests that are to be run. Required mainly for --bisect-chunk.
|
||||||
@ -2056,29 +2121,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
|
|
||||||
return result
|
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):
|
def runTests(self, options, onLaunch=None):
|
||||||
""" Prepare, configure, run tests and cleanup """
|
""" Prepare, configure, run tests and cleanup """
|
||||||
|
|
||||||
@ -2200,7 +2242,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
options,
|
options,
|
||||||
debuggerInfo is not None)
|
debuggerInfo is not None)
|
||||||
|
|
||||||
|
|
||||||
# If there are any Mulet-specific tests doing remote network access,
|
# If there are any Mulet-specific tests doing remote network access,
|
||||||
# we will not be aware since we are explicitely allowing this, as for
|
# we will not be aware since we are explicitely allowing this, as for
|
||||||
# B2G
|
# B2G
|
||||||
@ -2514,52 +2555,6 @@ class Mochitest(MochitestUtilsMixin):
|
|||||||
self.shutdownLeaks.log(message)
|
self.shutdownLeaks.log(message)
|
||||||
return 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):
|
def getDirectories(self, options):
|
||||||
"""
|
"""
|
||||||
Make the list of directories by parsing manifests
|
Make the list of directories by parsing manifests
|
||||||
@ -2581,7 +2576,7 @@ def run_test_harness(options):
|
|||||||
logger_options = {
|
logger_options = {
|
||||||
key: value for key, value in vars(options).iteritems()
|
key: value for key, value in vars(options).iteritems()
|
||||||
if key.startswith('log') or key == 'valgrind'}
|
if key.startswith('log') or key == 'valgrind'}
|
||||||
runner = Mochitest(logger_options)
|
runner = MochitestDesktop(logger_options)
|
||||||
|
|
||||||
options.runByDir = False
|
options.runByDir = False
|
||||||
|
|
||||||
|
@ -5,17 +5,14 @@
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import posixpath
|
import posixpath
|
||||||
import shutil
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
here = os.path.abspath(os.path.dirname(__file__))
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
sys.path.insert(0, here)
|
sys.path.insert(0, here)
|
||||||
|
|
||||||
from runtests import Mochitest
|
from runtests import MochitestBase
|
||||||
from runtests import MochitestUtilsMixin
|
|
||||||
from mochitest_options import MochitestArgumentParser
|
from mochitest_options import MochitestArgumentParser
|
||||||
from marionette import Marionette
|
from marionette import Marionette
|
||||||
from mozprofile import Profile, Preferences
|
from mozprofile import Profile, Preferences
|
||||||
@ -24,15 +21,22 @@ import mozinfo
|
|||||||
import mozleak
|
import mozleak
|
||||||
|
|
||||||
|
|
||||||
class B2GMochitest(MochitestUtilsMixin):
|
class MochitestB2G(MochitestBase):
|
||||||
|
"""
|
||||||
|
Mochitest class for b2g emulators and devices.
|
||||||
|
"""
|
||||||
marionette = None
|
marionette = None
|
||||||
|
remote_log = None
|
||||||
|
|
||||||
def __init__(self, marionette_args,
|
def __init__(self, marionette_args,
|
||||||
logger_options,
|
logger_options,
|
||||||
|
profile_data_dir,
|
||||||
|
local_binary_dir,
|
||||||
|
locations=os.path.join(here, 'server-locations.txt'),
|
||||||
out_of_process=True,
|
out_of_process=True,
|
||||||
profile_data_dir=None,
|
remote_test_root=None,
|
||||||
locations=os.path.join(here, 'server-locations.txt')):
|
remote_log_file=None):
|
||||||
super(B2GMochitest, self).__init__(logger_options)
|
MochitestBase.__init__(self, logger_options)
|
||||||
self.marionette_args = marionette_args
|
self.marionette_args = marionette_args
|
||||||
self.out_of_process = out_of_process
|
self.out_of_process = out_of_process
|
||||||
self.locations_file = locations
|
self.locations_file = locations
|
||||||
@ -42,8 +46,9 @@ class B2GMochitest(MochitestUtilsMixin):
|
|||||||
self.test_script_args = [self.out_of_process]
|
self.test_script_args = [self.out_of_process]
|
||||||
self.product = 'b2g'
|
self.product = 'b2g'
|
||||||
self.remote_chrome_test_dir = None
|
self.remote_chrome_test_dir = None
|
||||||
|
self.local_log = None
|
||||||
|
self.local_binary_dir = local_binary_dir
|
||||||
|
|
||||||
if profile_data_dir:
|
|
||||||
self.preferences = [
|
self.preferences = [
|
||||||
os.path.join(
|
os.path.join(
|
||||||
profile_data_dir,
|
profile_data_dir,
|
||||||
@ -59,21 +64,9 @@ class B2GMochitest(MochitestUtilsMixin):
|
|||||||
else:
|
else:
|
||||||
self.SERVER_STARTUP_TIMEOUT = 90
|
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):
|
def buildTestPath(self, options, testsToFilter=None):
|
||||||
if options.manifestFile != 'tests.json':
|
if options.manifestFile != 'tests.json':
|
||||||
super(B2GMochitest, self).buildTestPath(options, testsToFilter, disabled=False)
|
MochitestBase.buildTestPath(self, options, testsToFilter, disabled=False)
|
||||||
return self.buildTestURL(options)
|
return self.buildTestURL(options)
|
||||||
|
|
||||||
def build_profile(self, options):
|
def build_profile(self, options):
|
||||||
@ -107,11 +100,7 @@ class B2GMochitest(MochitestUtilsMixin):
|
|||||||
'proxy': {"remote": options.webServer}
|
'proxy': {"remote": options.webServer}
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.profile:
|
|
||||||
self.profile = Profile.clone(options.profile, **kwargs)
|
|
||||||
else:
|
|
||||||
self.profile = Profile(**kwargs)
|
self.profile = Profile(**kwargs)
|
||||||
|
|
||||||
options.profilePath = self.profile.profile
|
options.profilePath = self.profile.profile
|
||||||
# TODO bug 839108 - mozprofile should probably handle this
|
# TODO bug 839108 - mozprofile should probably handle this
|
||||||
manifest = self.addChromeToProfile(options)
|
manifest = self.addChromeToProfile(options)
|
||||||
@ -244,7 +233,7 @@ class B2GMochitest(MochitestUtilsMixin):
|
|||||||
if options.chrome:
|
if options.chrome:
|
||||||
self.app_ctx.dm.removeDir(self.remote_chrome_test_dir)
|
self.app_ctx.dm.removeDir(self.remote_chrome_test_dir)
|
||||||
self.app_ctx.dm.mkDir(self.remote_chrome_test_dir)
|
self.app_ctx.dm.mkDir(self.remote_chrome_test_dir)
|
||||||
local = super(B2GMochitest, self).getChromeTestDir(options)
|
local = MochitestBase.getChromeTestDir(self, options)
|
||||||
local = os.path.join(local, "chrome")
|
local = os.path.join(local, "chrome")
|
||||||
remote = self.remote_chrome_test_dir
|
remote = self.remote_chrome_test_dir
|
||||||
self.log.info(
|
self.log.info(
|
||||||
@ -319,27 +308,6 @@ class B2GMochitest(MochitestUtilsMixin):
|
|||||||
return self.remote_chrome_test_dir
|
return self.remote_chrome_test_dir
|
||||||
return 'dummy-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):
|
def cleanup(self, manifest, options):
|
||||||
if self.local_log:
|
if self.local_log:
|
||||||
self.app_ctx.dm.getFile(self.remote_log, self.local_log)
|
self.app_ctx.dm.getFile(self.remote_log, self.local_log)
|
||||||
@ -370,7 +338,7 @@ class B2GDeviceMochitest(B2GMochitest, Mochitest):
|
|||||||
options.utilityPath = self.local_binary_dir
|
options.utilityPath = self.local_binary_dir
|
||||||
options.profilePath = tempfile.mkdtemp()
|
options.profilePath = tempfile.mkdtemp()
|
||||||
|
|
||||||
MochitestUtilsMixin.startServers(self, options, debuggerInfo)
|
MochitestBase.startServers(self, options, debuggerInfo)
|
||||||
|
|
||||||
options.xrePath = savedXre
|
options.xrePath = savedXre
|
||||||
options.utilityPath = savedUtility
|
options.utilityPath = savedUtility
|
||||||
@ -380,72 +348,26 @@ class B2GDeviceMochitest(B2GMochitest, Mochitest):
|
|||||||
self.local_log = options.logFile
|
self.local_log = options.logFile
|
||||||
options.logFile = self.remote_log
|
options.logFile = self.remote_log
|
||||||
options.profilePath = self.profile.profile
|
options.profilePath = self.profile.profile
|
||||||
super(B2GDeviceMochitest, self).buildURLOptions(options, env)
|
MochitestBase.buildURLOptions(self, options, env)
|
||||||
|
|
||||||
|
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 "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.profilePath = self.app_ctx.remote_profile
|
||||||
options.logFile = self.local_log
|
options.logFile = self.local_log
|
||||||
|
|
||||||
|
|
||||||
class B2GDesktopMochitest(B2GMochitest, Mochitest):
|
def run_test_harness(options):
|
||||||
|
|
||||||
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
|
# create our Marionette instance
|
||||||
marionette_args = {
|
marionette_args = {
|
||||||
'adb_path': options.adbPath,
|
'adb_path': options.adbPath,
|
||||||
@ -466,7 +388,7 @@ def run_remote_mochitests(options):
|
|||||||
print "ERROR: Invalid options specified, use --help for a list of valid options"
|
print "ERROR: Invalid options specified, use --help for a list of valid options"
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
mochitest = B2GDeviceMochitest(
|
mochitest = MochitestB2G(
|
||||||
marionette_args,
|
marionette_args,
|
||||||
options,
|
options,
|
||||||
options.profile_data_dir,
|
options.profile_data_dir,
|
||||||
@ -495,45 +417,10 @@ def run_remote_mochitests(options):
|
|||||||
return retVal
|
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():
|
def main():
|
||||||
parser = MochitestArgumentParser(app='b2g')
|
parser = MochitestArgumentParser(app='b2g')
|
||||||
options = parser.parse_args()
|
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__":
|
if __name__ == "__main__":
|
||||||
sys.exit(main())
|
sys.exit(main())
|
||||||
|
@ -13,7 +13,7 @@ sys.path.insert(
|
|||||||
|
|
||||||
from automation import Automation
|
from automation import Automation
|
||||||
from remoteautomation import RemoteAutomation, fennecLogcatFilters
|
from remoteautomation import RemoteAutomation, fennecLogcatFilters
|
||||||
from runtests import Mochitest, MessageLogger
|
from runtests import MochitestDesktop, MessageLogger
|
||||||
from mochitest_options import MochitestArgumentParser
|
from mochitest_options import MochitestArgumentParser
|
||||||
|
|
||||||
import devicemanager
|
import devicemanager
|
||||||
@ -22,14 +22,15 @@ import mozinfo
|
|||||||
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
class MochiRemote(Mochitest):
|
# TODO inherit from MochitestBase instead
|
||||||
|
class MochiRemote(MochitestDesktop):
|
||||||
_automation = None
|
_automation = None
|
||||||
_dm = None
|
_dm = None
|
||||||
localProfile = None
|
localProfile = None
|
||||||
logMessages = []
|
logMessages = []
|
||||||
|
|
||||||
def __init__(self, automation, devmgr, options):
|
def __init__(self, automation, devmgr, options):
|
||||||
Mochitest.__init__(self, options)
|
MochitestDesktop.__init__(self, options)
|
||||||
|
|
||||||
self._automation = automation
|
self._automation = automation
|
||||||
self._dm = devmgr
|
self._dm = devmgr
|
||||||
@ -63,7 +64,7 @@ class MochiRemote(Mochitest):
|
|||||||
blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
|
blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
|
||||||
if blobberUploadDir:
|
if blobberUploadDir:
|
||||||
self._dm.getDirectory(self.remoteNSPR, blobberUploadDir)
|
self._dm.getDirectory(self.remoteNSPR, blobberUploadDir)
|
||||||
Mochitest.cleanup(self, options)
|
MochitestDesktop.cleanup(self, options)
|
||||||
|
|
||||||
def findPath(self, paths, filename=None):
|
def findPath(self, paths, filename=None):
|
||||||
for path in paths:
|
for path in paths:
|
||||||
@ -155,7 +156,7 @@ class MochiRemote(Mochitest):
|
|||||||
""" Create the servers on the host and start them up """
|
""" Create the servers on the host and start them up """
|
||||||
restoreRemotePaths = self.switchToLocalPaths(options)
|
restoreRemotePaths = self.switchToLocalPaths(options)
|
||||||
# ignoreSSLTunnelExts is a workaround for bug 1109310
|
# ignoreSSLTunnelExts is a workaround for bug 1109310
|
||||||
Mochitest.startServers(
|
MochitestDesktop.startServers(
|
||||||
self,
|
self,
|
||||||
options,
|
options,
|
||||||
debuggerInfo,
|
debuggerInfo,
|
||||||
@ -164,7 +165,7 @@ class MochiRemote(Mochitest):
|
|||||||
|
|
||||||
def buildProfile(self, options):
|
def buildProfile(self, options):
|
||||||
restoreRemotePaths = self.switchToLocalPaths(options)
|
restoreRemotePaths = self.switchToLocalPaths(options)
|
||||||
manifest = Mochitest.buildProfile(self, options)
|
manifest = MochitestDesktop.buildProfile(self, options)
|
||||||
self.localProfile = options.profilePath
|
self.localProfile = options.profilePath
|
||||||
self._dm.removeDir(self.remoteProfile)
|
self._dm.removeDir(self.remoteProfile)
|
||||||
|
|
||||||
@ -185,7 +186,7 @@ class MochiRemote(Mochitest):
|
|||||||
options.fileLevel = 'INFO'
|
options.fileLevel = 'INFO'
|
||||||
options.profilePath = self.localProfile
|
options.profilePath = self.localProfile
|
||||||
env["MOZ_HIDE_RESULTS_TABLE"] = "1"
|
env["MOZ_HIDE_RESULTS_TABLE"] = "1"
|
||||||
retVal = Mochitest.buildURLOptions(self, options, env)
|
retVal = MochitestDesktop.buildURLOptions(self, options, env)
|
||||||
|
|
||||||
# we really need testConfig.js (for browser chrome)
|
# we really need testConfig.js (for browser chrome)
|
||||||
try:
|
try:
|
||||||
@ -239,7 +240,7 @@ class MochiRemote(Mochitest):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def buildBrowserEnv(self, options, debugger=False):
|
def buildBrowserEnv(self, options, debugger=False):
|
||||||
browserEnv = Mochitest.buildBrowserEnv(
|
browserEnv = MochitestDesktop.buildBrowserEnv(
|
||||||
self,
|
self,
|
||||||
options,
|
options,
|
||||||
debugger=debugger)
|
debugger=debugger)
|
||||||
@ -248,7 +249,7 @@ class MochiRemote(Mochitest):
|
|||||||
del browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"]
|
del browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"]
|
||||||
if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
|
if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
|
||||||
del browserEnv["XPCOM_MEM_BLOAT_LOG"]
|
del browserEnv["XPCOM_MEM_BLOAT_LOG"]
|
||||||
# override nsprLogs to avoid processing in Mochitest base class
|
# override nsprLogs to avoid processing in MochitestDesktop base class
|
||||||
self.nsprLogs = None
|
self.nsprLogs = None
|
||||||
browserEnv["NSPR_LOG_FILE"] = os.path.join(
|
browserEnv["NSPR_LOG_FILE"] = os.path.join(
|
||||||
self.remoteNSPR,
|
self.remoteNSPR,
|
||||||
|
Loading…
Reference in New Issue
Block a user