mach_commands.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. # Integrates the xpcshell test runner with mach.
  5. from __future__ import absolute_import, unicode_literals, print_function
  6. import argparse
  7. import errno
  8. import os
  9. import sys
  10. from mozlog import structured
  11. from mozbuild.base import (
  12. MachCommandBase,
  13. MozbuildObject,
  14. MachCommandConditions as conditions,
  15. )
  16. from mach.decorators import (
  17. CommandProvider,
  18. Command,
  19. )
  20. from xpcshellcommandline import parser_desktop, parser_remote
  21. here = os.path.abspath(os.path.dirname(__file__))
  22. if sys.version_info[0] < 3:
  23. unicode_type = unicode
  24. else:
  25. unicode_type = str
  26. # This should probably be consolidated with similar classes in other test
  27. # runners.
  28. class InvalidTestPathError(Exception):
  29. """Exception raised when the test path is not valid."""
  30. class XPCShellRunner(MozbuildObject):
  31. """Run xpcshell tests."""
  32. def run_suite(self, **kwargs):
  33. return self._run_xpcshell_harness(**kwargs)
  34. def run_test(self, **kwargs):
  35. """Runs an individual xpcshell test."""
  36. # TODO Bug 794506 remove once mach integrates with virtualenv.
  37. build_path = os.path.join(self.topobjdir, 'build')
  38. if build_path not in sys.path:
  39. sys.path.append(build_path)
  40. src_build_path = os.path.join(self.topsrcdir, 'mozilla', 'build')
  41. if os.path.isdir(src_build_path):
  42. sys.path.append(src_build_path)
  43. return self.run_suite(**kwargs)
  44. def _run_xpcshell_harness(self, **kwargs):
  45. # Obtain a reference to the xpcshell test runner.
  46. import runxpcshelltests
  47. log = kwargs.pop("log")
  48. xpcshell = runxpcshelltests.XPCShellTests(log=log)
  49. self.log_manager.enable_unstructured()
  50. tests_dir = os.path.join(self.topobjdir, '_tests', 'xpcshell')
  51. # We want output from the test to be written immediately if we are only
  52. # running a single test.
  53. single_test = (len(kwargs["testPaths"]) == 1 and
  54. os.path.isfile(kwargs["testPaths"][0]) or
  55. kwargs["manifest"] and
  56. (len(kwargs["manifest"].test_paths()) == 1))
  57. if single_test:
  58. kwargs["verbose"] = True
  59. if kwargs["xpcshell"] is None:
  60. kwargs["xpcshell"] = self.get_binary_path('xpcshell')
  61. if kwargs["mozInfo"] is None:
  62. kwargs["mozInfo"] = os.path.join(self.topobjdir, 'mozinfo.json')
  63. if kwargs["symbolsPath"] is None:
  64. kwargs["symbolsPath"] = os.path.join(self.distdir, 'crashreporter-symbols')
  65. if kwargs["logfiles"] is None:
  66. kwargs["logfiles"] = False
  67. if kwargs["profileName"] is None:
  68. kwargs["profileName"] = "firefox"
  69. if kwargs["pluginsPath"] is None:
  70. kwargs['pluginsPath'] = os.path.join(self.distdir, 'plugins')
  71. if kwargs["testingModulesDir"] is None:
  72. kwargs["testingModulesDir"] = os.path.join(self.topobjdir, '_tests/modules')
  73. if kwargs["utility_path"] is None:
  74. kwargs['utility_path'] = self.bindir
  75. if kwargs["manifest"] is None:
  76. kwargs["manifest"] = os.path.join(tests_dir, "xpcshell.ini")
  77. if kwargs["failure_manifest"] is None:
  78. kwargs["failure_manifest"] = os.path.join(self.statedir, 'xpcshell.failures.ini')
  79. # Use the object directory for the temp directory to minimize the chance
  80. # of file scanning. The overhead from e.g. search indexers and anti-virus
  81. # scanners like Windows Defender can add tons of overhead to test execution.
  82. # We encourage people to disable these things in the object directory.
  83. temp_dir = os.path.join(self.topobjdir, 'temp')
  84. try:
  85. os.mkdir(temp_dir)
  86. except OSError as e:
  87. if e.errno != errno.EEXIST:
  88. raise
  89. kwargs['tempDir'] = temp_dir
  90. # Python through 2.7.2 has issues with unicode in some of the
  91. # arguments. Work around that.
  92. filtered_args = {}
  93. for k, v in kwargs.iteritems():
  94. if isinstance(v, unicode_type):
  95. v = v.encode('utf-8')
  96. if isinstance(k, unicode_type):
  97. k = k.encode('utf-8')
  98. filtered_args[k] = v
  99. result = xpcshell.runTests(**filtered_args)
  100. self.log_manager.disable_unstructured()
  101. if not result and not xpcshell.sequential:
  102. print("Tests were run in parallel. Try running with --sequential "
  103. "to make sure the failures were not caused by this.")
  104. return int(not result)
  105. class AndroidXPCShellRunner(MozbuildObject):
  106. """Get specified DeviceManager"""
  107. def get_devicemanager(self, devicemanager, ip, port, remote_test_root):
  108. import mozdevice
  109. dm = None
  110. if devicemanager == "adb":
  111. if ip:
  112. dm = mozdevice.DroidADB(ip, port, packageName=None, deviceRoot=remote_test_root)
  113. else:
  114. dm = mozdevice.DroidADB(packageName=None, deviceRoot=remote_test_root)
  115. else:
  116. if ip:
  117. dm = mozdevice.DroidSUT(ip, port, deviceRoot=remote_test_root)
  118. else:
  119. raise Exception("You must provide a device IP to connect to via the --ip option")
  120. return dm
  121. """Run Android xpcshell tests."""
  122. def run_test(self, **kwargs):
  123. # TODO Bug 794506 remove once mach integrates with virtualenv.
  124. build_path = os.path.join(self.topobjdir, 'build')
  125. if build_path not in sys.path:
  126. sys.path.append(build_path)
  127. import remotexpcshelltests
  128. dm = self.get_devicemanager(kwargs["dm_trans"], kwargs["deviceIP"], kwargs["devicePort"],
  129. kwargs["remoteTestRoot"])
  130. log = kwargs.pop("log")
  131. self.log_manager.enable_unstructured()
  132. if kwargs["xpcshell"] is None:
  133. kwargs["xpcshell"] = "xpcshell"
  134. if not kwargs["objdir"]:
  135. kwargs["objdir"] = self.topobjdir
  136. if not kwargs["localLib"]:
  137. kwargs["localLib"] = os.path.join(self.topobjdir, 'dist/fennec')
  138. if not kwargs["localBin"]:
  139. kwargs["localBin"] = os.path.join(self.topobjdir, 'dist/bin')
  140. if not kwargs["testingModulesDir"]:
  141. kwargs["testingModulesDir"] = os.path.join(self.topobjdir, '_tests/modules')
  142. if not kwargs["mozInfo"]:
  143. kwargs["mozInfo"] = os.path.join(self.topobjdir, 'mozinfo.json')
  144. if not kwargs["manifest"]:
  145. kwargs["manifest"] = os.path.join(self.topobjdir, '_tests/xpcshell/xpcshell.ini')
  146. if not kwargs["symbolsPath"]:
  147. kwargs["symbolsPath"] = os.path.join(self.distdir, 'crashreporter-symbols')
  148. if not kwargs["localAPK"]:
  149. for file_name in os.listdir(os.path.join(kwargs["objdir"], "dist")):
  150. if file_name.endswith(".apk") and file_name.startswith("fennec"):
  151. kwargs["localAPK"] = os.path.join(kwargs["objdir"], "dist", file_name)
  152. print ("using APK: %s" % kwargs["localAPK"])
  153. break
  154. else:
  155. raise Exception("APK not found in objdir. You must specify an APK.")
  156. if not kwargs["sequential"]:
  157. kwargs["sequential"] = True
  158. options = argparse.Namespace(**kwargs)
  159. xpcshell = remotexpcshelltests.XPCShellRemote(dm, options, log)
  160. result = xpcshell.runTests(testClass=remotexpcshelltests.RemoteXPCShellTestThread,
  161. mobileArgs=xpcshell.mobileArgs,
  162. **vars(options))
  163. self.log_manager.disable_unstructured()
  164. return int(not result)
  165. def get_parser():
  166. build_obj = MozbuildObject.from_environment(cwd=here)
  167. if conditions.is_android(build_obj):
  168. return parser_remote()
  169. else:
  170. return parser_desktop()
  171. @CommandProvider
  172. class MachCommands(MachCommandBase):
  173. @Command('xpcshell-test', category='testing',
  174. description='Run XPCOM Shell tests (API direct unit testing)',
  175. conditions=[lambda *args: True],
  176. parser=get_parser)
  177. def run_xpcshell_test(self, test_objects=None, **params):
  178. from mozbuild.controller.building import BuildDriver
  179. if test_objects is not None:
  180. from manifestparser import TestManifest
  181. m = TestManifest()
  182. m.tests.extend(test_objects)
  183. params['manifest'] = m
  184. driver = self._spawn(BuildDriver)
  185. driver.install_tests(test_objects)
  186. # We should probably have a utility function to ensure the tree is
  187. # ready to run tests. Until then, we just create the state dir (in
  188. # case the tree wasn't built with mach).
  189. self._ensure_state_subdir_exists('.')
  190. params['log'] = structured.commandline.setup_logging("XPCShellTests",
  191. params,
  192. {"mach": sys.stdout},
  193. {"verbose": True})
  194. if conditions.is_android(self):
  195. from mozrunner.devices.android_device import verify_android_device
  196. verify_android_device(self)
  197. xpcshell = self._spawn(AndroidXPCShellRunner)
  198. else:
  199. xpcshell = self._spawn(XPCShellRunner)
  200. xpcshell.cwd = self._mach_context.cwd
  201. try:
  202. return xpcshell.run_test(**params)
  203. except InvalidTestPathError as e:
  204. print(e.message)
  205. return 1