runtestsremote.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  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. import os
  5. import sys
  6. import traceback
  7. sys.path.insert(
  8. 0, os.path.abspath(
  9. os.path.realpath(
  10. os.path.dirname(__file__))))
  11. from automation import Automation
  12. from remoteautomation import RemoteAutomation, fennecLogcatFilters
  13. from runtests import MochitestDesktop, MessageLogger
  14. from mochitest_options import MochitestArgumentParser
  15. import mozdevice
  16. import mozinfo
  17. SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
  18. class MochiRemote(MochitestDesktop):
  19. _automation = None
  20. _dm = None
  21. localProfile = None
  22. logMessages = []
  23. def __init__(self, automation, devmgr, options):
  24. MochitestDesktop.__init__(self, options)
  25. self._automation = automation
  26. self._dm = devmgr
  27. self.environment = self._automation.environment
  28. self.remoteProfile = os.path.join(options.remoteTestRoot, "profile/")
  29. self.remoteModulesDir = os.path.join(options.remoteTestRoot, "modules/")
  30. self._automation.setRemoteProfile(self.remoteProfile)
  31. self.remoteLog = options.remoteLogFile
  32. self.localLog = options.logFile
  33. self._automation.deleteANRs()
  34. self._automation.deleteTombstones()
  35. self.certdbNew = True
  36. self.remoteMozLog = os.path.join(options.remoteTestRoot, "mozlog")
  37. self._dm.removeDir(self.remoteMozLog)
  38. self._dm.mkDir(self.remoteMozLog)
  39. self.remoteChromeTestDir = os.path.join(
  40. options.remoteTestRoot,
  41. "chrome")
  42. self._dm.removeDir(self.remoteChromeTestDir)
  43. self._dm.mkDir(self.remoteChromeTestDir)
  44. def cleanup(self, options):
  45. if self._dm.fileExists(self.remoteLog):
  46. self._dm.getFile(self.remoteLog, self.localLog)
  47. self._dm.removeFile(self.remoteLog)
  48. else:
  49. self.log.warning(
  50. "Unable to retrieve log file (%s) from remote device" %
  51. self.remoteLog)
  52. self._dm.removeDir(self.remoteProfile)
  53. self._dm.removeDir(self.remoteChromeTestDir)
  54. blobberUploadDir = os.environ.get('MOZ_UPLOAD_DIR', None)
  55. if blobberUploadDir:
  56. self._dm.getDirectory(self.remoteMozLog, blobberUploadDir)
  57. MochitestDesktop.cleanup(self, options)
  58. def findPath(self, paths, filename=None):
  59. for path in paths:
  60. p = path
  61. if filename:
  62. p = os.path.join(p, filename)
  63. if os.path.exists(self.getFullPath(p)):
  64. return path
  65. return None
  66. def makeLocalAutomation(self):
  67. localAutomation = Automation()
  68. localAutomation.IS_WIN32 = False
  69. localAutomation.IS_LINUX = False
  70. localAutomation.IS_MAC = False
  71. localAutomation.UNIXISH = False
  72. hostos = sys.platform
  73. if (hostos == 'mac' or hostos == 'darwin'):
  74. localAutomation.IS_MAC = True
  75. elif (hostos == 'linux' or hostos == 'linux2'):
  76. localAutomation.IS_LINUX = True
  77. localAutomation.UNIXISH = True
  78. elif (hostos == 'win32' or hostos == 'win64'):
  79. localAutomation.BIN_SUFFIX = ".exe"
  80. localAutomation.IS_WIN32 = True
  81. return localAutomation
  82. # This seems kludgy, but this class uses paths from the remote host in the
  83. # options, except when calling up to the base class, which doesn't
  84. # understand the distinction. This switches out the remote values for local
  85. # ones that the base class understands. This is necessary for the web
  86. # server, SSL tunnel and profile building functions.
  87. def switchToLocalPaths(self, options):
  88. """ Set local paths in the options, return a function that will restore remote values """
  89. remoteXrePath = options.xrePath
  90. remoteProfilePath = options.profilePath
  91. remoteUtilityPath = options.utilityPath
  92. localAutomation = self.makeLocalAutomation()
  93. paths = [
  94. options.xrePath,
  95. localAutomation.DIST_BIN,
  96. self._automation._product,
  97. os.path.join('..', self._automation._product)
  98. ]
  99. options.xrePath = self.findPath(paths)
  100. if options.xrePath is None:
  101. self.log.error(
  102. "unable to find xulrunner path for %s, please specify with --xre-path" %
  103. os.name)
  104. sys.exit(1)
  105. xpcshell = "xpcshell"
  106. if (os.name == "nt"):
  107. xpcshell += ".exe"
  108. if options.utilityPath:
  109. paths = [options.utilityPath, options.xrePath]
  110. else:
  111. paths = [options.xrePath]
  112. options.utilityPath = self.findPath(paths, xpcshell)
  113. if options.utilityPath is None:
  114. self.log.error(
  115. "unable to find utility path for %s, please specify with --utility-path" %
  116. os.name)
  117. sys.exit(1)
  118. xpcshell_path = os.path.join(options.utilityPath, xpcshell)
  119. if localAutomation.elf_arm(xpcshell_path):
  120. self.log.error('xpcshell at %s is an ARM binary; please use '
  121. 'the --utility-path argument to specify the path '
  122. 'to a desktop version.' % xpcshell_path)
  123. sys.exit(1)
  124. if self.localProfile:
  125. options.profilePath = self.localProfile
  126. else:
  127. options.profilePath = None
  128. def fixup():
  129. options.xrePath = remoteXrePath
  130. options.utilityPath = remoteUtilityPath
  131. options.profilePath = remoteProfilePath
  132. return fixup
  133. def startServers(self, options, debuggerInfo):
  134. """ Create the servers on the host and start them up """
  135. restoreRemotePaths = self.switchToLocalPaths(options)
  136. # ignoreSSLTunnelExts is a workaround for bug 1109310
  137. MochitestDesktop.startServers(
  138. self,
  139. options,
  140. debuggerInfo,
  141. ignoreSSLTunnelExts=True)
  142. restoreRemotePaths()
  143. def buildProfile(self, options):
  144. restoreRemotePaths = self.switchToLocalPaths(options)
  145. if options.testingModulesDir:
  146. try:
  147. self._dm.pushDir(options.testingModulesDir, self.remoteModulesDir)
  148. self._dm.chmodDir(self.remoteModulesDir)
  149. except mozdevice.DMError:
  150. self.log.error(
  151. "Automation Error: Unable to copy test modules to device.")
  152. raise
  153. savedTestingModulesDir = options.testingModulesDir
  154. options.testingModulesDir = self.remoteModulesDir
  155. else:
  156. savedTestingModulesDir = None
  157. manifest = MochitestDesktop.buildProfile(self, options)
  158. if savedTestingModulesDir:
  159. options.testingModulesDir = savedTestingModulesDir
  160. self.localProfile = options.profilePath
  161. restoreRemotePaths()
  162. options.profilePath = self.remoteProfile
  163. return manifest
  164. def addChromeToProfile(self, options):
  165. manifest = MochitestDesktop.addChromeToProfile(self, options)
  166. # Support Firefox (browser), SeaMonkey (navigator), and Webapp Runtime (webapp).
  167. if options.flavor == 'chrome':
  168. # append overlay to chrome.manifest
  169. chrome = ("overlay chrome://browser/content/browser.xul "
  170. "chrome://mochikit/content/browser-test-overlay.xul")
  171. path = os.path.join(options.profilePath, 'extensions', 'staged',
  172. 'mochikit@mozilla.org', 'chrome.manifest')
  173. with open(path, "a") as f:
  174. f.write(chrome)
  175. return manifest
  176. def buildURLOptions(self, options, env):
  177. self.localLog = options.logFile
  178. options.logFile = self.remoteLog
  179. options.fileLevel = 'INFO'
  180. options.profilePath = self.localProfile
  181. env["MOZ_HIDE_RESULTS_TABLE"] = "1"
  182. retVal = MochitestDesktop.buildURLOptions(self, options, env)
  183. # we really need testConfig.js (for browser chrome)
  184. try:
  185. self._dm.pushDir(options.profilePath, self.remoteProfile)
  186. self._dm.chmodDir(self.remoteProfile)
  187. except mozdevice.DMError:
  188. self.log.error(
  189. "Automation Error: Unable to copy profile to device.")
  190. raise
  191. options.profilePath = self.remoteProfile
  192. options.logFile = self.localLog
  193. return retVal
  194. def getChromeTestDir(self, options):
  195. local = super(MochiRemote, self).getChromeTestDir(options)
  196. local = os.path.join(local, "chrome")
  197. remote = self.remoteChromeTestDir
  198. if options.flavor == 'chrome':
  199. self.log.info("pushing %s to %s on device..." % (local, remote))
  200. self._dm.pushDir(local, remote)
  201. return remote
  202. def getLogFilePath(self, logFile):
  203. return logFile
  204. def printDeviceInfo(self, printLogcat=False):
  205. try:
  206. if printLogcat:
  207. logcat = self._dm.getLogcat(
  208. filterOutRegexps=fennecLogcatFilters)
  209. self.log.info(
  210. '\n' +
  211. ''.join(logcat).decode(
  212. 'utf-8',
  213. 'replace'))
  214. self.log.info("Device info:")
  215. devinfo = self._dm.getInfo()
  216. for category in devinfo:
  217. if type(devinfo[category]) is list:
  218. self.log.info(" %s:" % category)
  219. for item in devinfo[category]:
  220. self.log.info(" %s" % item)
  221. else:
  222. self.log.info(" %s: %s" % (category, devinfo[category]))
  223. self.log.info("Test root: %s" % self._dm.deviceRoot)
  224. except mozdevice.DMError:
  225. self.log.warning("Error getting device information")
  226. def getGMPPluginPath(self, options):
  227. # TODO: bug 1149374
  228. return None
  229. def buildBrowserEnv(self, options, debugger=False):
  230. browserEnv = MochitestDesktop.buildBrowserEnv(
  231. self,
  232. options,
  233. debugger=debugger)
  234. # remove desktop environment not used on device
  235. if "MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA" in browserEnv:
  236. del browserEnv["MOZ_WIN_INHERIT_STD_HANDLES_PRE_VISTA"]
  237. if "XPCOM_MEM_BLOAT_LOG" in browserEnv:
  238. del browserEnv["XPCOM_MEM_BLOAT_LOG"]
  239. # override mozLogs to avoid processing in MochitestDesktop base class
  240. self.mozLogs = None
  241. browserEnv["MOZ_LOG_FILE"] = os.path.join(
  242. self.remoteMozLog,
  243. self.mozLogName)
  244. return browserEnv
  245. def runApp(self, *args, **kwargs):
  246. """front-end automation.py's `runApp` functionality until FennecRunner is written"""
  247. # automation.py/remoteautomation `runApp` takes the profile path,
  248. # whereas runtest.py's `runApp` takes a mozprofile object.
  249. if 'profileDir' not in kwargs and 'profile' in kwargs:
  250. kwargs['profileDir'] = kwargs.pop('profile').profile
  251. # remove args not supported by automation.py
  252. kwargs.pop('marionette_args', None)
  253. return self._automation.runApp(*args, **kwargs)
  254. def run_test_harness(parser, options):
  255. parser.validate(options)
  256. message_logger = MessageLogger(logger=None)
  257. process_args = {'messageLogger': message_logger}
  258. auto = RemoteAutomation(None, "fennec", processArgs=process_args)
  259. if options is None:
  260. raise ValueError("Invalid options specified, use --help for a list of valid options")
  261. options.runByDir = False
  262. # roboextender is used by mochitest-chrome tests like test_java_addons.html,
  263. # but not by any plain mochitests
  264. if options.flavor != 'chrome':
  265. options.extensionsToExclude.append('roboextender@mozilla.org')
  266. dm = options.dm
  267. auto.setDeviceManager(dm)
  268. mochitest = MochiRemote(auto, dm, options)
  269. log = mochitest.log
  270. message_logger.logger = log
  271. mochitest.message_logger = message_logger
  272. # Check that Firefox is installed
  273. expected = options.app.split('/')[-1]
  274. installed = dm.shellCheckOutput(['pm', 'list', 'packages', expected])
  275. if expected not in installed:
  276. log.error("%s is not installed on this device" % expected)
  277. return 1
  278. productPieces = options.remoteProductName.split('.')
  279. if (productPieces is not None):
  280. auto.setProduct(productPieces[0])
  281. else:
  282. auto.setProduct(options.remoteProductName)
  283. auto.setAppName(options.remoteappname)
  284. logParent = os.path.dirname(options.remoteLogFile)
  285. dm.mkDir(logParent)
  286. auto.setRemoteLog(options.remoteLogFile)
  287. auto.setServerInfo(options.webServer, options.httpPort, options.sslPort)
  288. if options.log_mach is None:
  289. mochitest.printDeviceInfo()
  290. # Add Android version (SDK level) to mozinfo so that manifest entries
  291. # can be conditional on android_version.
  292. androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
  293. log.info(
  294. "Android sdk version '%s'; will use this to filter manifests" %
  295. str(androidVersion))
  296. mozinfo.info['android_version'] = androidVersion
  297. deviceRoot = dm.deviceRoot
  298. if options.dmdPath:
  299. dmdLibrary = "libdmd.so"
  300. dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
  301. dm.removeFile(dmdPathOnDevice)
  302. dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
  303. options.dmdPath = deviceRoot
  304. options.dumpOutputDirectory = deviceRoot
  305. procName = options.app.split('/')[-1]
  306. dm.killProcess(procName)
  307. mochitest.mozLogName = "moz.log"
  308. try:
  309. dm.recordLogcat()
  310. retVal = mochitest.runTests(options)
  311. except:
  312. log.error("Automation Error: Exception caught while running tests")
  313. traceback.print_exc()
  314. mochitest.stopServers()
  315. try:
  316. mochitest.cleanup(options)
  317. except mozdevice.DMError:
  318. # device error cleaning up... oh well!
  319. pass
  320. retVal = 1
  321. if options.log_mach is None:
  322. mochitest.printDeviceInfo(printLogcat=True)
  323. message_logger.finish()
  324. return retVal
  325. def main(args=sys.argv[1:]):
  326. parser = MochitestArgumentParser(app='android')
  327. options = parser.parse_args(args)
  328. return run_test_harness(parser, options)
  329. if __name__ == "__main__":
  330. sys.exit(main())