testing/xpcshell/remotexpcshelltests.py

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

mercurial