remotexpcshelltests.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #!/usr/bin/env python
  2. #
  3. # This Source Code Form is subject to the terms of the Mozilla Public
  4. # License, v. 2.0. If a copy of the MPL was not distributed with this
  5. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  6. import posixpath
  7. import sys, os
  8. import subprocess
  9. import runxpcshelltests as xpcshell
  10. import tempfile
  11. import time
  12. from zipfile import ZipFile
  13. from mozlog import commandline
  14. import shutil
  15. import mozdevice
  16. import mozfile
  17. import mozinfo
  18. from xpcshellcommandline import parser_remote
  19. here = os.path.dirname(os.path.abspath(__file__))
  20. def remoteJoin(path1, path2):
  21. return posixpath.join(path1, path2)
  22. class RemoteXPCShellTestThread(xpcshell.XPCShellTestThread):
  23. def __init__(self, *args, **kwargs):
  24. xpcshell.XPCShellTestThread.__init__(self, *args, **kwargs)
  25. self.shellReturnCode = None
  26. # embed the mobile params from the harness into the TestThread
  27. mobileArgs = kwargs.get('mobileArgs')
  28. for key in mobileArgs:
  29. setattr(self, key, mobileArgs[key])
  30. def buildCmdTestFile(self, name):
  31. remoteDir = self.remoteForLocal(os.path.dirname(name))
  32. if remoteDir == self.remoteHere:
  33. remoteName = os.path.basename(name)
  34. else:
  35. remoteName = remoteJoin(remoteDir, os.path.basename(name))
  36. return ['-e', 'const _TEST_FILE = ["%s"];' %
  37. remoteName.replace('\\', '/')]
  38. def remoteForLocal(self, local):
  39. for mapping in self.pathMapping:
  40. if (os.path.abspath(mapping.local) == os.path.abspath(local)):
  41. return mapping.remote
  42. return local
  43. def setupTempDir(self):
  44. # make sure the temp dir exists
  45. self.clearRemoteDir(self.remoteTmpDir)
  46. # env var is set in buildEnvironment
  47. return self.remoteTmpDir
  48. def setupPluginsDir(self):
  49. if not os.path.isdir(self.pluginsPath):
  50. return None
  51. # making sure tmp dir is set up
  52. self.setupTempDir()
  53. pluginsDir = remoteJoin(self.remoteTmpDir, "plugins")
  54. self.device.pushDir(self.pluginsPath, pluginsDir)
  55. if self.interactive:
  56. self.log.info("plugins dir is %s" % pluginsDir)
  57. return pluginsDir
  58. def setupProfileDir(self):
  59. self.clearRemoteDir(self.profileDir)
  60. if self.interactive or self.singleFile:
  61. self.log.info("profile dir is %s" % self.profileDir)
  62. return self.profileDir
  63. def setupMozinfoJS(self):
  64. local = tempfile.mktemp()
  65. mozinfo.output_to_file(local)
  66. mozInfoJSPath = remoteJoin(self.profileDir, "mozinfo.json")
  67. self.device.pushFile(local, mozInfoJSPath)
  68. os.remove(local)
  69. return mozInfoJSPath
  70. def logCommand(self, name, completeCmd, testdir):
  71. self.log.info("%s | full command: %r" % (name, completeCmd))
  72. self.log.info("%s | current directory: %r" % (name, self.remoteHere))
  73. self.log.info("%s | environment: %s" % (name, self.env))
  74. def getHeadAndTailFiles(self, test):
  75. """Override parent method to find files on remote device.
  76. Obtains lists of head- and tail files. Returns a tuple containing
  77. a list of head files and a list of tail files.
  78. """
  79. def sanitize_list(s, kind):
  80. for f in s.strip().split(' '):
  81. f = f.strip()
  82. if len(f) < 1:
  83. continue
  84. path = remoteJoin(self.remoteHere, f)
  85. # skip check for file existence: the convenience of discovering
  86. # a missing file does not justify the time cost of the round trip
  87. # to the device
  88. yield path
  89. self.remoteHere = self.remoteForLocal(test['here'])
  90. headlist = test.get('head', '')
  91. taillist = test.get('tail', '')
  92. return (list(sanitize_list(headlist, 'head')),
  93. list(sanitize_list(taillist, 'tail')))
  94. def buildXpcsCmd(self):
  95. # change base class' paths to remote paths and use base class to build command
  96. self.xpcshell = remoteJoin(self.remoteBinDir, "xpcw")
  97. self.headJSPath = remoteJoin(self.remoteScriptsDir, 'head.js')
  98. self.httpdJSPath = remoteJoin(self.remoteComponentsDir, 'httpd.js')
  99. self.httpdManifest = remoteJoin(self.remoteComponentsDir, 'httpd.manifest')
  100. self.testingModulesDir = self.remoteModulesDir
  101. self.testharnessdir = self.remoteScriptsDir
  102. xpcshell.XPCShellTestThread.buildXpcsCmd(self)
  103. # remove "-g <dir> -a <dir>" and add "--greomni <apk>"
  104. del(self.xpcsCmd[1:5])
  105. if self.options.localAPK:
  106. self.xpcsCmd.insert(3, '--greomni')
  107. self.xpcsCmd.insert(4, self.remoteAPK)
  108. if self.remoteDebugger:
  109. # for example, "/data/local/gdbserver" "localhost:12345"
  110. self.xpcsCmd = [
  111. self.remoteDebugger,
  112. self.remoteDebuggerArgs,
  113. self.xpcsCmd]
  114. def killTimeout(self, proc):
  115. self.kill(proc)
  116. def launchProcess(self, cmd, stdout, stderr, env, cwd, timeout=None):
  117. self.timedout = False
  118. cmd.insert(1, self.remoteHere)
  119. outputFile = "xpcshelloutput"
  120. with open(outputFile, 'w+') as f:
  121. try:
  122. self.shellReturnCode = self.device.shell(cmd, f, timeout=timeout+10)
  123. except mozdevice.DMError as e:
  124. if self.timedout:
  125. # If the test timed out, there is a good chance the SUTagent also
  126. # timed out and failed to return a return code, generating a
  127. # DMError. Ignore the DMError to simplify the error report.
  128. self.shellReturnCode = None
  129. pass
  130. else:
  131. raise e
  132. # The device manager may have timed out waiting for xpcshell.
  133. # Guard against an accumulation of hung processes by killing
  134. # them here. Note also that IPC tests may spawn new instances
  135. # of xpcshell.
  136. self.device.killProcess("xpcshell")
  137. return outputFile
  138. def checkForCrashes(self,
  139. dump_directory,
  140. symbols_path,
  141. test_name=None):
  142. if not self.device.dirExists(self.remoteMinidumpDir):
  143. # The minidumps directory is automatically created when Fennec
  144. # (first) starts, so its lack of presence is a hint that
  145. # something went wrong.
  146. print "Automation Error: No crash directory (%s) found on remote device" % self.remoteMinidumpDir
  147. # Whilst no crash was found, the run should still display as a failure
  148. return True
  149. with mozfile.TemporaryDirectory() as dumpDir:
  150. self.device.getDirectory(self.remoteMinidumpDir, dumpDir)
  151. crashed = xpcshell.XPCShellTestThread.checkForCrashes(self, dumpDir, symbols_path, test_name)
  152. self.clearRemoteDir(self.remoteMinidumpDir)
  153. return crashed
  154. def communicate(self, proc):
  155. f = open(proc, "r")
  156. contents = f.read()
  157. f.close()
  158. os.remove(proc)
  159. return contents, ""
  160. def poll(self, proc):
  161. if self.device.processExist("xpcshell") is None:
  162. return self.getReturnCode(proc)
  163. # Process is still running
  164. return None
  165. def kill(self, proc):
  166. return self.device.killProcess("xpcshell", True)
  167. def getReturnCode(self, proc):
  168. if self.shellReturnCode is not None:
  169. return self.shellReturnCode
  170. else:
  171. return -1
  172. def removeDir(self, dirname):
  173. self.device.removeDir(dirname)
  174. def clearRemoteDir(self, remoteDir):
  175. out = ""
  176. try:
  177. out = self.device.shellCheckOutput([self.remoteClearDirScript, remoteDir])
  178. except mozdevice.DMError:
  179. self.log.info("unable to delete %s: '%s'" % (remoteDir, str(out)))
  180. self.log.info("retrying after 10 seconds...")
  181. time.sleep(10)
  182. try:
  183. out = self.device.shellCheckOutput([self.remoteClearDirScript, remoteDir])
  184. except mozdevice.DMError:
  185. self.log.error("failed to delete %s: '%s'" % (remoteDir, str(out)))
  186. #TODO: consider creating a separate log dir. We don't have the test file structure,
  187. # so we use filename.log. Would rather see ./logs/filename.log
  188. def createLogFile(self, test, stdout):
  189. try:
  190. f = None
  191. filename = test.replace('\\', '/').split('/')[-1] + ".log"
  192. f = open(filename, "w")
  193. f.write(stdout)
  194. finally:
  195. if f is not None:
  196. f.close()
  197. # A specialization of XPCShellTests that runs tests on an Android device
  198. # via devicemanager.
  199. class XPCShellRemote(xpcshell.XPCShellTests, object):
  200. def __init__(self, devmgr, options, log):
  201. xpcshell.XPCShellTests.__init__(self, log)
  202. # Add Android version (SDK level) to mozinfo so that manifest entries
  203. # can be conditional on android_version.
  204. androidVersion = devmgr.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
  205. mozinfo.info['android_version'] = androidVersion
  206. self.localLib = options.localLib
  207. self.localBin = options.localBin
  208. self.options = options
  209. self.device = devmgr
  210. self.pathMapping = []
  211. self.remoteTestRoot = "%s/xpc" % self.device.deviceRoot
  212. # remoteBinDir contains xpcshell and its wrapper script, both of which must
  213. # be executable. Since +x permissions cannot usually be set on /mnt/sdcard,
  214. # and the test root may be on /mnt/sdcard, remoteBinDir is set to be on
  215. # /data/local, always.
  216. self.remoteBinDir = "/data/local/xpcb"
  217. # Terse directory names are used here ("c" for the components directory)
  218. # to minimize the length of the command line used to execute
  219. # xpcshell on the remote device. adb has a limit to the number
  220. # of characters used in a shell command, and the xpcshell command
  221. # line can be quite complex.
  222. self.remoteTmpDir = remoteJoin(self.remoteTestRoot, "tmp")
  223. self.remoteScriptsDir = self.remoteTestRoot
  224. self.remoteComponentsDir = remoteJoin(self.remoteTestRoot, "c")
  225. self.remoteModulesDir = remoteJoin(self.remoteTestRoot, "m")
  226. self.remoteMinidumpDir = remoteJoin(self.remoteTestRoot, "minidumps")
  227. self.remoteClearDirScript = remoteJoin(self.remoteBinDir, "cleardir")
  228. self.profileDir = remoteJoin(self.remoteTestRoot, "p")
  229. self.remoteDebugger = options.debugger
  230. self.remoteDebuggerArgs = options.debuggerArgs
  231. self.testingModulesDir = options.testingModulesDir
  232. self.env = {}
  233. if self.options.objdir:
  234. self.xpcDir = os.path.join(self.options.objdir, "_tests/xpcshell")
  235. elif os.path.isdir(os.path.join(here, 'tests')):
  236. self.xpcDir = os.path.join(here, 'tests')
  237. else:
  238. print >> sys.stderr, "Couldn't find local xpcshell test directory"
  239. sys.exit(1)
  240. if options.localAPK:
  241. self.localAPKContents = ZipFile(options.localAPK)
  242. if options.setup:
  243. self.setupTestDir()
  244. self.setupUtilities()
  245. self.setupModules()
  246. self.setupMinidumpDir()
  247. self.remoteAPK = None
  248. if options.localAPK:
  249. self.remoteAPK = remoteJoin(self.remoteBinDir, os.path.basename(options.localAPK))
  250. self.setAppRoot()
  251. # data that needs to be passed to the RemoteXPCShellTestThread
  252. self.mobileArgs = {
  253. 'device': self.device,
  254. 'remoteBinDir': self.remoteBinDir,
  255. 'remoteScriptsDir': self.remoteScriptsDir,
  256. 'remoteComponentsDir': self.remoteComponentsDir,
  257. 'remoteModulesDir': self.remoteModulesDir,
  258. 'options': self.options,
  259. 'remoteDebugger': self.remoteDebugger,
  260. 'pathMapping': self.pathMapping,
  261. 'profileDir': self.profileDir,
  262. 'remoteTmpDir': self.remoteTmpDir,
  263. 'remoteMinidumpDir': self.remoteMinidumpDir,
  264. 'remoteClearDirScript': self.remoteClearDirScript,
  265. }
  266. if self.remoteAPK:
  267. self.mobileArgs['remoteAPK'] = self.remoteAPK
  268. def setLD_LIBRARY_PATH(self):
  269. self.env["LD_LIBRARY_PATH"] = self.remoteBinDir
  270. def pushWrapper(self):
  271. # Rather than executing xpcshell directly, this wrapper script is
  272. # used. By setting environment variables and the cwd in the script,
  273. # the length of the per-test command line is shortened. This is
  274. # often important when using ADB, as there is a limit to the length
  275. # of the ADB command line.
  276. localWrapper = tempfile.mktemp()
  277. f = open(localWrapper, "w")
  278. f.write("#!/system/bin/sh\n")
  279. for envkey, envval in self.env.iteritems():
  280. f.write("export %s=%s\n" % (envkey, envval))
  281. f.writelines([
  282. "cd $1\n",
  283. "echo xpcw: cd $1\n",
  284. "shift\n",
  285. "echo xpcw: xpcshell \"$@\"\n",
  286. "%s/xpcshell \"$@\"\n" % self.remoteBinDir])
  287. f.close()
  288. remoteWrapper = remoteJoin(self.remoteBinDir, "xpcw")
  289. self.device.pushFile(localWrapper, remoteWrapper)
  290. os.remove(localWrapper)
  291. # Removing and re-creating a directory is a common operation which
  292. # can be implemented more efficiently with a shell script.
  293. localWrapper = tempfile.mktemp()
  294. f = open(localWrapper, "w")
  295. # The directory may not exist initially, so rm may fail. 'rm -f' is not
  296. # supported on some Androids. Similarly, 'test' and 'if [ -d ]' are not
  297. # universally available, so we just ignore errors from rm.
  298. f.writelines([
  299. "#!/system/bin/sh\n",
  300. "rm -r \"$1\"\n",
  301. "mkdir \"$1\"\n"])
  302. f.close()
  303. self.device.pushFile(localWrapper, self.remoteClearDirScript)
  304. os.remove(localWrapper)
  305. self.device.chmodDir(self.remoteBinDir)
  306. def buildEnvironment(self):
  307. self.buildCoreEnvironment()
  308. self.setLD_LIBRARY_PATH()
  309. self.env["MOZ_LINKER_CACHE"] = self.remoteBinDir
  310. if self.options.localAPK and self.appRoot:
  311. self.env["GRE_HOME"] = self.appRoot
  312. self.env["XPCSHELL_TEST_PROFILE_DIR"] = self.profileDir
  313. self.env["TMPDIR"] = self.remoteTmpDir
  314. self.env["HOME"] = self.profileDir
  315. self.env["XPCSHELL_TEST_TEMP_DIR"] = self.remoteTmpDir
  316. self.env["XPCSHELL_MINIDUMP_DIR"] = self.remoteMinidumpDir
  317. if self.options.setup:
  318. self.pushWrapper()
  319. def setAppRoot(self):
  320. # Determine the application root directory associated with the package
  321. # name used by the Fennec APK.
  322. self.appRoot = None
  323. packageName = None
  324. if self.options.localAPK:
  325. try:
  326. packageName = self.localAPKContents.read("package-name.txt")
  327. if packageName:
  328. self.appRoot = self.device.getAppRoot(packageName.strip())
  329. except Exception as detail:
  330. print "unable to determine app root: " + str(detail)
  331. pass
  332. return None
  333. def setupUtilities(self):
  334. if (not self.device.dirExists(self.remoteBinDir)):
  335. # device.mkDir may fail here where shellCheckOutput may succeed -- see bug 817235
  336. try:
  337. self.device.shellCheckOutput(["mkdir", self.remoteBinDir]);
  338. except mozdevice.DMError:
  339. # Might get a permission error; try again as root, if available
  340. self.device.shellCheckOutput(["mkdir", self.remoteBinDir], root=True);
  341. self.device.shellCheckOutput(["chmod", "777", self.remoteBinDir], root=True);
  342. remotePrefDir = remoteJoin(self.remoteBinDir, "defaults/pref")
  343. if (self.device.dirExists(self.remoteTmpDir)):
  344. self.device.removeDir(self.remoteTmpDir)
  345. self.device.mkDir(self.remoteTmpDir)
  346. if (not self.device.dirExists(remotePrefDir)):
  347. self.device.mkDirs(remoteJoin(remotePrefDir, "extra"))
  348. if (not self.device.dirExists(self.remoteScriptsDir)):
  349. self.device.mkDir(self.remoteScriptsDir)
  350. if (not self.device.dirExists(self.remoteComponentsDir)):
  351. self.device.mkDir(self.remoteComponentsDir)
  352. local = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'head.js')
  353. remoteFile = remoteJoin(self.remoteScriptsDir, "head.js")
  354. self.device.pushFile(local, remoteFile)
  355. # The xpcshell binary is required for all tests. Additional binaries
  356. # are required for some tests. This list should be similar to
  357. # TEST_HARNESS_BINS in testing/mochitest/Makefile.in.
  358. binaries = ["xpcshell",
  359. "ssltunnel",
  360. "certutil",
  361. "pk12util",
  362. "BadCertServer",
  363. "OCSPStaplingServer",
  364. "GenerateOCSPResponse"]
  365. for fname in binaries:
  366. local = os.path.join(self.localBin, fname)
  367. if os.path.isfile(local):
  368. print >> sys.stderr, "Pushing %s.." % fname
  369. remoteFile = remoteJoin(self.remoteBinDir, fname)
  370. self.device.pushFile(local, remoteFile)
  371. else:
  372. print >> sys.stderr, "*** Expected binary %s not found in %s!" % (fname, self.localBin)
  373. local = os.path.join(self.localBin, "components/httpd.js")
  374. remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.js")
  375. self.device.pushFile(local, remoteFile)
  376. local = os.path.join(self.localBin, "components/httpd.manifest")
  377. remoteFile = remoteJoin(self.remoteComponentsDir, "httpd.manifest")
  378. self.device.pushFile(local, remoteFile)
  379. local = os.path.join(self.localBin, "components/test_necko.xpt")
  380. remoteFile = remoteJoin(self.remoteComponentsDir, "test_necko.xpt")
  381. self.device.pushFile(local, remoteFile)
  382. if self.options.localAPK:
  383. remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(self.options.localAPK))
  384. self.device.pushFile(self.options.localAPK, remoteFile)
  385. self.pushLibs()
  386. def pushLibs(self):
  387. pushed_libs_count = 0
  388. if self.options.localAPK:
  389. try:
  390. dir = tempfile.mkdtemp()
  391. for info in self.localAPKContents.infolist():
  392. if info.filename.endswith(".so"):
  393. print >> sys.stderr, "Pushing %s.." % info.filename
  394. remoteFile = remoteJoin(self.remoteBinDir, os.path.basename(info.filename))
  395. self.localAPKContents.extract(info, dir)
  396. localFile = os.path.join(dir, info.filename)
  397. with open(localFile) as f:
  398. # Decompress xz-compressed file.
  399. if f.read(5)[1:] == '7zXZ':
  400. cmd = ['xz', '-df', '--suffix', '.so', localFile]
  401. subprocess.check_output(cmd)
  402. # xz strips the ".so" file suffix.
  403. os.rename(localFile[:-3], localFile)
  404. self.device.pushFile(localFile, remoteFile)
  405. pushed_libs_count += 1
  406. finally:
  407. shutil.rmtree(dir)
  408. return pushed_libs_count
  409. for file in os.listdir(self.localLib):
  410. if (file.endswith(".so")):
  411. print >> sys.stderr, "Pushing %s.." % file
  412. if 'libxul' in file:
  413. print >> sys.stderr, "This is a big file, it could take a while."
  414. localFile = os.path.join(self.localLib, file)
  415. remoteFile = remoteJoin(self.remoteBinDir, file)
  416. self.device.pushFile(localFile, remoteFile)
  417. pushed_libs_count += 1
  418. # Additional libraries may be found in a sub-directory such as "lib/armeabi-v7a"
  419. localArmLib = os.path.join(self.localLib, "lib")
  420. if os.path.exists(localArmLib):
  421. for root, dirs, files in os.walk(localArmLib):
  422. for file in files:
  423. if (file.endswith(".so")):
  424. print >> sys.stderr, "Pushing %s.." % file
  425. localFile = os.path.join(root, file)
  426. remoteFile = remoteJoin(self.remoteBinDir, file)
  427. self.device.pushFile(localFile, remoteFile)
  428. pushed_libs_count += 1
  429. return pushed_libs_count
  430. def setupModules(self):
  431. if self.testingModulesDir:
  432. self.device.pushDir(self.testingModulesDir, self.remoteModulesDir)
  433. def setupTestDir(self):
  434. print 'pushing %s' % self.xpcDir
  435. try:
  436. # The tests directory can be quite large: 5000 files and growing!
  437. # Sometimes - like on a low-end aws instance running an emulator - the push
  438. # may exceed the default 5 minute timeout, so we increase it here to 10 minutes.
  439. self.device.pushDir(self.xpcDir, self.remoteScriptsDir, timeout=600, retryLimit=10)
  440. except TypeError:
  441. # Foopies have an older mozdevice ver without retryLimit
  442. self.device.pushDir(self.xpcDir, self.remoteScriptsDir)
  443. def setupMinidumpDir(self):
  444. if self.device.dirExists(self.remoteMinidumpDir):
  445. self.device.removeDir(self.remoteMinidumpDir)
  446. self.device.mkDir(self.remoteMinidumpDir)
  447. def buildTestList(self, test_tags=None, test_paths=None):
  448. xpcshell.XPCShellTests.buildTestList(self, test_tags=test_tags, test_paths=test_paths)
  449. uniqueTestPaths = set([])
  450. for test in self.alltests:
  451. uniqueTestPaths.add(test['here'])
  452. for testdir in uniqueTestPaths:
  453. abbrevTestDir = os.path.relpath(testdir, self.xpcDir)
  454. remoteScriptDir = remoteJoin(self.remoteScriptsDir, abbrevTestDir)
  455. self.pathMapping.append(PathMapping(testdir, remoteScriptDir))
  456. def verifyRemoteOptions(parser, options):
  457. if options.localLib is None:
  458. if options.localAPK and options.objdir:
  459. for path in ['dist/fennec', 'fennec/lib']:
  460. options.localLib = os.path.join(options.objdir, path)
  461. if os.path.isdir(options.localLib):
  462. break
  463. else:
  464. parser.error("Couldn't find local library dir, specify --local-lib-dir")
  465. elif options.objdir:
  466. options.localLib = os.path.join(options.objdir, 'dist/bin')
  467. elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
  468. # assume tests are being run from a tests.zip
  469. options.localLib = os.path.abspath(os.path.join(here, '..', 'bin'))
  470. else:
  471. parser.error("Couldn't find local library dir, specify --local-lib-dir")
  472. if options.localBin is None:
  473. if options.objdir:
  474. for path in ['dist/bin', 'bin']:
  475. options.localBin = os.path.join(options.objdir, path)
  476. if os.path.isdir(options.localBin):
  477. break
  478. else:
  479. parser.error("Couldn't find local binary dir, specify --local-bin-dir")
  480. elif os.path.isfile(os.path.join(here, '..', 'bin', 'xpcshell')):
  481. # assume tests are being run from a tests.zip
  482. options.localBin = os.path.abspath(os.path.join(here, '..', 'bin'))
  483. else:
  484. parser.error("Couldn't find local binary dir, specify --local-bin-dir")
  485. return options
  486. class PathMapping:
  487. def __init__(self, localDir, remoteDir):
  488. self.local = localDir
  489. self.remote = remoteDir
  490. def main():
  491. if sys.version_info < (2,7):
  492. print >>sys.stderr, "Error: You must use python version 2.7 or newer but less than 3.0"
  493. sys.exit(1)
  494. parser = parser_remote()
  495. options = parser.parse_args()
  496. if not options.localAPK:
  497. for file in os.listdir(os.path.join(options.objdir, "dist")):
  498. if (file.endswith(".apk") and file.startswith("fennec")):
  499. options.localAPK = os.path.join(options.objdir, "dist")
  500. options.localAPK = os.path.join(options.localAPK, file)
  501. print >>sys.stderr, "using APK: " + options.localAPK
  502. break
  503. else:
  504. print >>sys.stderr, "Error: please specify an APK"
  505. sys.exit(1)
  506. options = verifyRemoteOptions(parser, options)
  507. log = commandline.setup_logging("Remote XPCShell",
  508. options,
  509. {"tbpl": sys.stdout})
  510. if options.dm_trans == "adb":
  511. if options.deviceIP:
  512. dm = mozdevice.DroidADB(options.deviceIP, options.devicePort, packageName=None, deviceRoot=options.remoteTestRoot)
  513. else:
  514. dm = mozdevice.DroidADB(packageName=None, deviceRoot=options.remoteTestRoot)
  515. else:
  516. if not options.deviceIP:
  517. print "Error: you must provide a device IP to connect to via the --device option"
  518. sys.exit(1)
  519. dm = mozdevice.DroidSUT(options.deviceIP, options.devicePort, deviceRoot=options.remoteTestRoot)
  520. if options.interactive and not options.testPath:
  521. print >>sys.stderr, "Error: You must specify a test filename in interactive mode!"
  522. sys.exit(1)
  523. if options.xpcshell is None:
  524. options.xpcshell = "xpcshell"
  525. xpcsh = XPCShellRemote(dm, options, log)
  526. # we don't run concurrent tests on mobile
  527. options.sequential = True
  528. if not xpcsh.runTests(testClass=RemoteXPCShellTestThread,
  529. mobileArgs=xpcsh.mobileArgs,
  530. **vars(options)):
  531. sys.exit(1)
  532. if __name__ == '__main__':
  533. main()