Bug 869095 - Bump in-tree mozdevice to 0.25;r=jmaher
authorWilliam Lachance <wlach@mozilla.com>
Tue, 07 May 2013 11:09:46 -0400
changeset 142068 86cdfca59c81867641f90739127f9dfd2bf1b37d
parent 142067 f8e522d6991f53212b6a326d07dbc76ce0e00a6f
child 142069 9e5ad7ec4b6ec9a6d473b1527d69b3c091dc4f18
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher
bugs869095
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 869095 - Bump in-tree mozdevice to 0.25;r=jmaher
testing/mozbase/mozdevice/mozdevice/dmcli.py
testing/mozbase/mozdevice/mozdevice/droid.py
testing/mozbase/mozdevice/setup.py
--- a/testing/mozbase/mozdevice/mozdevice/dmcli.py
+++ b/testing/mozbase/mozdevice/mozdevice/dmcli.py
@@ -14,42 +14,44 @@ import sys
 import mozdevice
 import mozlog
 import argparse
 
 class DMCli(object):
 
     def __init__(self):
         self.commands = { 'install': { 'function': self.install,
-                                       'args': [ { 'name': 'file', 'nargs': None } ],
+                                       'args': [ { 'name': 'file' } ],
                                        'help': 'push this package file to the device and install it' },
                           'uninstall': { 'function': self.uninstall,
-                                         'args': [ { 'name': 'packagename', 'nargs': None } ],
+                                         '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', 'nargs': None },
-                                                   { 'name': 'activity_name',
-                                                     'nargs': None },
+                                         'args': [ { 'name': 'appname' },
+                                                   { 'name': 'activity_name' },
                                                    { 'name': '--intent',
                                                      'action': 'store',
                                                      'default': 'android.intent.action.VIEW' },
                                                    { 'name': '--url',
-                                                     'action': 'store' }
+                                                     'action': 'store' },
+                                                   { 'name': '--no-fail-if-running',
+                                                     'action': 'store_true',
+                                                     'help': 'Don\'t fail if application is already running' }
                                                 ],
                                       'help': 'launches application on device' },
                           'push': { 'function': self.push,
-                                    'args': [ { 'name': 'local_file', 'nargs': None },
-                                              { 'name': 'remote_file', 'nargs': None }
+                                    'args': [ { 'name': 'local_file' },
+                                              { 'name': 'remote_file' }
                                               ],
                                     'help': 'copy file/dir to device' },
                           'pull': { 'function': self.pull,
-                                    'args': [ { 'name': 'local_file', 'nargs': None },
+                                    'args': [ { 'name': 'local_file' },
                                               { 'name': 'remote_file', 'nargs': '?' } ],
                                     'help': 'copy file/dir from device' },
                           'shell': { 'function': self.shell,
                                     'args': [ { 'name': 'command', 'nargs': argparse.REMAINDER } ],
                                     'help': 'run shell command on device' },
                           'info': { 'function': self.getinfo,
                                     'args': [ { 'name': 'directive', 'nargs': '?' } ],
                                     'help': 'get information on specified '
@@ -58,59 +60,63 @@ class DMCli(object):
                                     },
                           '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', 'nargs': None } ],
+                                  'args': [ { 'name': 'remote_dir' } ],
                                   'help': 'list files on device'
                                 },
                           'rm': { 'function': self.removefile,
-                                  'args': [ { 'name': 'remote_file', 'nargs': None } ],
+                                  'args': [ { 'name': 'remote_file' } ],
                                   'help': 'remove file from device'
                                 },
                           'isdir': { 'function': self.isdir,
-                                     'args': [ { 'name': 'remote_dir', 'nargs': None } ],
+                                     'args': [ { 'name': 'remote_dir' } ],
                                      'help': 'print if remote file is a directory'
                                 },
                           'mkdir': { 'function': self.mkdir,
-                                     'args': [ { 'name': 'remote_dir', 'nargs': None } ],
+                                     'args': [ { 'name': 'remote_dir' } ],
                                      'help': 'makes a directory on device'
                                 },
                           'rmdir': { 'function': self.rmdir,
-                                     'args': [ { 'name': 'remote_dir', 'nargs': None } ],
+                                     'args': [ { 'name': 'remote_dir' } ],
                                      'help': 'recursively remove directory from device'
                                 },
                           'screencap': { 'function': self.screencap,
-                                         'args': [ { 'name': 'png_file', 'nargs': None } ],
+                                         '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'
                                    },
                           'isfile': { 'function': self.isfile,
-                                      'args': [ { 'name': 'remote_file', 'nargs': None } ],
+                                      'args': [ { 'name': 'remote_file' } ],
                                       'help': 'check whether a file exists on the device'
                                    },
                           'launchfennec': { 'function': self.launchfennec,
-                                            'args': [ { 'name': 'appname', 'nargs': None },
+                                            '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' } ],
+                                                      { 'name': '--mozenv', 'action': 'store' },
+                                                      { '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'
                                    }
                           }
 
@@ -157,18 +163,18 @@ class DMCli(object):
                             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']:
-                    subparser.add_argument(arg['name'], nargs=arg.get('nargs'),
-                                           action=arg.get('action'))
+                    kwargs = { k: v for k,v in arg.items() if k is not 'name' }
+                    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 = mozlog.ERROR
@@ -224,17 +230,18 @@ class DMCli(object):
         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, args.url)
+                                  args.intent, url=args.url,
+                                  failIfRunning=(not args.no_fail_if_running))
 
     def kill(self, args):
         for name in args.process_name:
             self.dm.killProcess(name)
 
     def shell(self, args, root=False):
         buf = StringIO.StringIO()
         self.dm.shell(args.command, buf, root=root)
@@ -304,17 +311,18 @@ class DMCli(object):
             print "TRUE"
             return
         print "FALSE"
         return errno.ENOENT
 
     def launchfennec(self, args):
         self.dm.launchFennec(args.appname, intent=args.intent,
                              mozEnv=args.mozenv,
-                             extraArgs=args.extra_args, url=args.url)
+                             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:]):
--- a/testing/mozbase/mozdevice/mozdevice/droid.py
+++ b/testing/mozbase/mozdevice/mozdevice/droid.py
@@ -14,33 +14,39 @@ from devicemanager import DMError
 
 class DroidMixin(object):
     """Mixin to extend DeviceManager with Android-specific functionality"""
 
     def _getExtraAmStartArgs(self):
         return []
 
     def launchApplication(self, appName, activityName, intent, url=None,
-                          extras=None):
+                          extras=None, wait=True, failIfRunning=True):
         """
         Launches an Android application
 
         :param appName: Name of application (e.g. `com.android.chrome`)
         :param activityName: Name of activity to launch (e.g. `.Main`)
         :param intent: Intent to launch application with
         :param url: URL to open
         :param extras: Dictionary of extra arguments to launch application with
+        :param wait: If True, wait for application to start before returning
+        :param failIfRunning: Raise an exception if instance of application is already running
         """
-        # only one instance of an application may be running at once
-        if self.processExist(appName):
+
+        # If failIfRunning is True, we throw an exception here. Only one
+        # instance of an application can be running at once on Android,
+        # starting a new instance may not be what we want depending on what
+        # we want to do
+        if failIfRunning and self.processExist(appName):
             raise DMError("Only one instance of an application may be running "
                           "at once")
 
         acmd = [ "am", "start" ] + self._getExtraAmStartArgs() + \
-            ["-W", "-n", "%s/%s" % (appName, activityName)]
+            ["-W" if wait else '', "-n", "%s/%s" % (appName, activityName)]
 
         if intent:
             acmd.extend(["-a", intent])
 
         if extras:
             for (key, val) in extras.iteritems():
                 if type(val) is int:
                     extraTypeParam = "--ei"
@@ -59,58 +65,62 @@ class DroidMixin(object):
         shellOutput = StringIO.StringIO()
         if self.shell(acmd, shellOutput) == 0:
             return
 
         shellOutput.seek(0)
         raise DMError("Unable to launch application (shell output: '%s')" % shellOutput.read())
 
     def launchFennec(self, appName, intent="android.intent.action.VIEW",
-                     mozEnv=None, extraArgs=None, url=None):
+                     mozEnv=None, extraArgs=None, url=None, wait=True,
+                     failIfRunning=True):
         """
         Convenience method to launch Fennec on Android with various debugging
         arguments
 
         :param appName: Name of fennec application (e.g. `org.mozilla.fennec`)
         :param intent: Intent to launch application with
         :param mozEnv: Mozilla specific environment to pass into application
         :param extraArgs: Extra arguments to be parsed by fennec
         :param url: URL to open
+        :param wait: If True, wait for application to start before returning
+        :param failIfRunning: Raise an exception if instance of application is already running
         """
         extras = {}
 
         if mozEnv:
             # mozEnv is expected to be a dictionary of environment variables: Fennec
             # itself will set them when launched
             for (envCnt, (envkey, envval)) in enumerate(mozEnv.iteritems()):
                 extras["env" + str(envCnt)] = envkey + "=" + envval
 
         # Additional command line arguments that fennec will read and use (e.g.
         # with a custom profile)
         if extraArgs:
             extras['args'] = " ".join(extraArgs)
 
-        self.launchApplication(appName, ".App", intent, url=url, extras=extras)
+        self.launchApplication(appName, ".App", intent, url=url, extras=extras,
+                               wait=wait, failIfRunning=failIfRunning)
 
 class DroidADB(DeviceManagerADB, DroidMixin):
 
     def getTopActivity(self):
         package = None
         data = self.shellCheckOutput(["dumpsys", "window", "input"])
         # "dumpsys window input" produces many lines of input. The top/foreground
         # activity is indicated by something like:
         #   mFocusedApp=AppWindowToken{483e6db0 token=HistoryRecord{484dcad8 com.mozilla.SUTAgentAndroid/.SUTAgentAndroid}}
         # Extract this line, ending in the forward slash:
         m = re.search('mFocusedApp(.+)/', data)
         if m:
-             line = m.group(0)
-             # Extract package name: string of non-whitespace ending in forward slash
-             m = re.search('(\S+)/$', line)
-             if m:
-                 package = m.group(1)
+            line = m.group(0)
+            # Extract package name: string of non-whitespace ending in forward slash
+            m = re.search('(\S+)/$', line)
+            if m:
+                package = m.group(1)
         return package
 
 class DroidSUT(DeviceManagerSUT, DroidMixin):
 
     def _getExtraAmStartArgs(self):
         # in versions of android in jellybean and beyond, the agent may run as
         # a different process than the one that started the app. In this case,
         # we need to get back the original user serial number and then pass
--- a/testing/mozbase/mozdevice/setup.py
+++ b/testing/mozbase/mozdevice/setup.py
@@ -1,15 +1,15 @@
 # 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/.
 
 from setuptools import setup
 
-PACKAGE_VERSION = '0.22'
+PACKAGE_VERSION = '0.25'
 
 setup(name='mozdevice',
       version=PACKAGE_VERSION,
       description="Mozilla-authored device management",
       long_description="see http://mozbase.readthedocs.org/",
       classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
       keywords='',
       author='Mozilla Automation and Testing Team',