diff options
Diffstat (limited to 'testing/mozbase/mozdevice/mozdevice/dmcli.py')
-rw-r--r-- | testing/mozbase/mozdevice/mozdevice/dmcli.py | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/testing/mozbase/mozdevice/mozdevice/dmcli.py b/testing/mozbase/mozdevice/mozdevice/dmcli.py new file mode 100644 index 000000000..7ba65e842 --- /dev/null +++ b/testing/mozbase/mozdevice/mozdevice/dmcli.py @@ -0,0 +1,382 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +Command-line client to control a device +""" + +import errno +import logging +import os +import posixpath +import StringIO +import sys +import mozdevice +import mozlog +import argparse + + +class DMCli(object): + + def __init__(self): + self.commands = {'deviceroot': {'function': self.deviceroot, + 'help': 'get device root directory for storing temporary ' + 'files'}, + 'install': {'function': self.install, + 'args': [{'name': 'file'}], + 'help': 'push this package file to the device' + ' and install it'}, + 'uninstall': {'function': self.uninstall, + 'args': [{'name': 'packagename'}], + 'help': 'uninstall the named app from the device'}, + 'killapp': {'function': self.kill, + 'args': [{'name': 'process_name', 'nargs': '*'}], + 'help': 'kills any processes with name(s) on device'}, + 'launchapp': {'function': self.launchapp, + 'args': [{'name': 'appname'}, + {'name': 'activity_name'}, + {'name': '--intent', + 'action': 'store', + 'default': 'android.intent.action.VIEW'}, + {'name': '--url', + 'action': 'store'}, + {'name': '--no-fail-if-running', + 'action': 'store_true', + 'help': 'Don\'t fail if application is' + ' already running'} + ], + 'help': 'launches application on device'}, + 'listapps': {'function': self.listapps, + 'help': 'list applications on device'}, + 'push': {'function': self.push, + 'args': [{'name': 'local_file'}, + {'name': 'remote_file'} + ], + 'help': 'copy file/dir to device'}, + 'pull': {'function': self.pull, + 'args': [{'name': 'local_file'}, + {'name': 'remote_file', 'nargs': '?'}], + 'help': 'copy file/dir from device'}, + 'shell': {'function': self.shell, + 'args': [{'name': 'command', 'nargs': argparse.REMAINDER}, + {'name': '--root', 'action': 'store_true', + 'help': 'Run command as root'}], + 'help': 'run shell command on device'}, + 'info': {'function': self.getinfo, + 'args': [{'name': 'directive', 'nargs': '?'}], + 'help': 'get information on specified ' + 'aspect of the device (if no argument ' + 'given, print all available information)' + }, + 'ps': {'function': self.processlist, + 'help': 'get information on running processes on device' + }, + 'logcat': {'function': self.logcat, + 'help': 'get logcat from device' + }, + 'ls': {'function': self.listfiles, + 'args': [{'name': 'remote_dir'}], + 'help': 'list files on device' + }, + 'rm': {'function': self.removefile, + 'args': [{'name': 'remote_file'}], + 'help': 'remove file from device' + }, + 'isdir': {'function': self.isdir, + 'args': [{'name': 'remote_dir'}], + 'help': 'print if remote file is a directory' + }, + 'mkdir': {'function': self.mkdir, + 'args': [{'name': 'remote_dir'}], + 'help': 'makes a directory on device' + }, + 'rmdir': {'function': self.rmdir, + 'args': [{'name': 'remote_dir'}], + 'help': 'recursively remove directory from device' + }, + 'screencap': {'function': self.screencap, + 'args': [{'name': 'png_file'}], + 'help': 'capture screenshot of device in action' + }, + 'sutver': {'function': self.sutver, + 'help': 'SUTAgent\'s product name and version (SUT only)' + }, + 'clearlogcat': {'function': self.clearlogcat, + 'help': 'clear the logcat' + }, + 'reboot': {'function': self.reboot, + 'help': 'reboot the device', + 'args': [{'name': '--wait', + 'action': 'store_true', + 'help': 'Wait for device to come back up' + ' before exiting'}] + + }, + 'isfile': {'function': self.isfile, + 'args': [{'name': 'remote_file'}], + 'help': 'check whether a file exists on the device' + }, + 'launchfennec': {'function': self.launchfennec, + 'args': [{'name': 'appname'}, + {'name': '--intent', 'action': 'store', + 'default': 'android.intent.action.VIEW'}, + {'name': '--url', 'action': 'store'}, + {'name': '--extra-args', 'action': 'store'}, + {'name': '--mozenv', 'action': 'store', + 'help': 'Gecko environment variables to set' + ' in "KEY1=VAL1 KEY2=VAL2" format'}, + {'name': '--no-fail-if-running', + 'action': 'store_true', + 'help': 'Don\'t fail if application is ' + 'already running'} + ], + 'help': 'launch fennec' + }, + 'getip': {'function': self.getip, + 'args': [{'name': 'interface', 'nargs': '*'}], + 'help': 'get the ip address of the device' + } + } + + self.parser = argparse.ArgumentParser() + self.add_options(self.parser) + self.add_commands(self.parser) + mozlog.commandline.add_logging_group(self.parser) + + def run(self, args=sys.argv[1:]): + args = self.parser.parse_args() + + mozlog.commandline.setup_logging( + 'mozdevice', args, {'mach': sys.stdout}) + + if args.dmtype == "sut" and not args.host and not args.hwid: + self.parser.error("Must specify device ip in TEST_DEVICE or " + "with --host option with SUT") + + self.dm = self.getDevice(dmtype=args.dmtype, hwid=args.hwid, + host=args.host, port=args.port, + verbose=args.verbose) + + ret = args.func(args) + if ret is None: + ret = 0 + + sys.exit(ret) + + def add_options(self, parser): + parser.add_argument("-v", "--verbose", action="store_true", + help="Verbose output from DeviceManager", + default=bool(os.environ.get('VERBOSE'))) + parser.add_argument("--host", action="store", + help="Device hostname (only if using TCP/IP, " + "defaults to TEST_DEVICE environment " + "variable if present)", + default=os.environ.get('TEST_DEVICE')) + parser.add_argument("-p", "--port", action="store", + type=int, + help="Custom device port (if using SUTAgent or " + "adb-over-tcp)", default=None) + parser.add_argument("-m", "--dmtype", action="store", + help="DeviceManager type (adb or sut, defaults " + "to DM_TRANS environment variable, if " + "present, or adb)", + default=os.environ.get('DM_TRANS', 'adb')) + parser.add_argument("-d", "--hwid", action="store", + help="HWID", default=None) + parser.add_argument("--package-name", action="store", + help="Packagename (if using DeviceManagerADB)", + default=None) + + def add_commands(self, parser): + subparsers = parser.add_subparsers(title="Commands", metavar="<command>") + for (commandname, commandprops) in sorted(self.commands.iteritems()): + subparser = subparsers.add_parser(commandname, help=commandprops['help']) + if commandprops.get('args'): + for arg in commandprops['args']: + # this is more elegant but doesn't work in python 2.6 + # (which we still use on tbpl @ mozilla where we install + # this package) + # kwargs = { k: v for k,v in arg.items() if k is not 'name' } + kwargs = {} + for (k, v) in arg.items(): + if k is not 'name': + kwargs[k] = v + subparser.add_argument(arg['name'], **kwargs) + subparser.set_defaults(func=commandprops['function']) + + def getDevice(self, dmtype="adb", hwid=None, host=None, port=None, + packagename=None, verbose=False): + ''' + Returns a device with the specified parameters + ''' + logLevel = logging.ERROR + if verbose: + logLevel = logging.DEBUG + + if hwid: + return mozdevice.DroidConnectByHWID(hwid, logLevel=logLevel) + + if dmtype == "adb": + if host and not port: + port = 5555 + return mozdevice.DroidADB(packageName=packagename, + host=host, port=port, + logLevel=logLevel) + elif dmtype == "sut": + if not host: + self.parser.error("Must specify host with SUT!") + if not port: + port = 20701 + return mozdevice.DroidSUT(host=host, port=port, + logLevel=logLevel) + else: + self.parser.error("Unknown device manager type: %s" % type) + + def deviceroot(self, args): + print self.dm.deviceRoot + + def push(self, args): + (src, dest) = (args.local_file, args.remote_file) + if os.path.isdir(src): + self.dm.pushDir(src, dest) + else: + dest_is_dir = dest[-1] == '/' or self.dm.dirExists(dest) + dest = posixpath.normpath(dest) + if dest_is_dir: + dest = posixpath.join(dest, os.path.basename(src)) + self.dm.pushFile(src, dest) + + def pull(self, args): + (src, dest) = (args.local_file, args.remote_file) + if not self.dm.fileExists(src): + print 'No such file or directory' + return + if not dest: + dest = posixpath.basename(src) + if self.dm.dirExists(src): + self.dm.getDirectory(src, dest) + else: + self.dm.getFile(src, dest) + + def install(self, args): + basename = os.path.basename(args.file) + app_path_on_device = posixpath.join(self.dm.deviceRoot, + basename) + self.dm.pushFile(args.file, app_path_on_device) + self.dm.installApp(app_path_on_device) + + def uninstall(self, args): + self.dm.uninstallApp(args.packagename) + + def launchapp(self, args): + self.dm.launchApplication(args.appname, args.activity_name, + args.intent, url=args.url, + failIfRunning=(not args.no_fail_if_running)) + + def listapps(self, args): + for app in self.dm.getInstalledApps(): + print app + + def stopapp(self, args): + self.dm.stopApplication(args.appname) + + def kill(self, args): + for name in args.process_name: + self.dm.killProcess(name) + + def shell(self, args): + buf = StringIO.StringIO() + self.dm.shell(args.command, buf, root=args.root) + print str(buf.getvalue()[0:-1]).rstrip() + + def getinfo(self, args): + info = self.dm.getInfo(directive=args.directive) + for (infokey, infoitem) in sorted(info.iteritems()): + if infokey == "process": + pass # skip process list: get that through ps + elif args.directive is None: + print "%s: %s" % (infokey.upper(), infoitem) + else: + print infoitem + + def logcat(self, args): + print ''.join(self.dm.getLogcat()) + + def clearlogcat(self, args): + self.dm.recordLogcat() + + def reboot(self, args): + self.dm.reboot(wait=args.wait) + + def processlist(self, args): + pslist = self.dm.getProcessList() + for ps in pslist: + print " ".join(str(i) for i in ps) + + def listfiles(self, args): + filelist = self.dm.listFiles(args.remote_dir) + for file in filelist: + print file + + def removefile(self, args): + self.dm.removeFile(args.remote_file) + + def isdir(self, args): + if self.dm.dirExists(args.remote_dir): + print "TRUE" + return + + print "FALSE" + return errno.ENOTDIR + + def mkdir(self, args): + self.dm.mkDir(args.remote_dir) + + def rmdir(self, args): + self.dm.removeDir(args.remote_dir) + + def screencap(self, args): + self.dm.saveScreenshot(args.png_file) + + def sutver(self, args): + if args.dmtype == 'sut': + print '%s Version %s' % (self.dm.agentProductName, + self.dm.agentVersion) + else: + print 'Must use SUT transport to get SUT version.' + + def isfile(self, args): + if self.dm.fileExists(args.remote_file): + print "TRUE" + return + print "FALSE" + return errno.ENOENT + + def launchfennec(self, args): + mozEnv = None + if args.mozenv: + mozEnv = {} + keyvals = args.mozenv.split() + for keyval in keyvals: + (key, _, val) = keyval.partition("=") + mozEnv[key] = val + self.dm.launchFennec(args.appname, intent=args.intent, + mozEnv=mozEnv, + extraArgs=args.extra_args, url=args.url, + failIfRunning=(not args.no_fail_if_running)) + + def getip(self, args): + if args.interface: + print(self.dm.getIP(args.interface)) + else: + print(self.dm.getIP()) + + +def cli(args=sys.argv[1:]): + # process the command line + cli = DMCli() + cli.run(args) + +if __name__ == '__main__': + cli() |