Bug 772105 - Give a better error if failed to push a file with devicemanagerSUT;r=jmaher
authorWilliam Lachance <wlachance@mozilla.com>
Wed, 11 Jul 2012 10:24:05 -0400
changeset 98954 a667224e0861c2ee3118a4b2aeda0b4379c293fd
parent 98953 addac3423b97454610ef3cffb44f824c94d7b135
child 98955 f8a42c0ed58ac6d5fed58d32c74ccbe5a9f8b10b
push id11742
push userwlachance@mozilla.com
push dateWed, 11 Jul 2012 14:25:42 +0000
treeherdermozilla-inbound@a667224e0861 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjmaher
bugs772105
milestone16.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 772105 - Give a better error if failed to push a file with devicemanagerSUT;r=jmaher
build/mobile/devicemanagerSUT.py
--- a/build/mobile/devicemanagerSUT.py
+++ b/build/mobile/devicemanagerSUT.py
@@ -30,17 +30,17 @@ class DeviceManagerSUT(DeviceManager):
   port = 0
   debug = 2 
   retries = 0
   tempRoot = os.getcwd()
   base_prompt = '$>'
   base_prompt_re = '\$\>'
   prompt_sep = '\x00'
   prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')'
-  agentErrorRE = re.compile('^##AGENT-WARNING##.*')
+  agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)')
 
   # TODO: member variable to indicate error conditions.
   # This should be set to a standard error from the errno module.
   # So, for example, when an error occurs because of a missing file/directory,
   # before returning, the function would do something like 'self.error = errno.ENOENT'.
   # The error would be set where appropriate--so sendCMD() could set socket errors,
   # pushFile() and other file-related commands could set filesystem errors, etc.
 
@@ -50,25 +50,21 @@ class DeviceManagerSUT(DeviceManager):
     self.retrylimit = retrylimit
     self.retries = 0
     self._sock = None
     if self.getDeviceRoot() == None:
         raise BaseException("Failed to connect to SUT Agent and retrieve the device root.")
 
   def _cmdNeedsResponse(self, cmd):
     """ Not all commands need a response from the agent:
-        * if the cmd matches the pushRE then it is the first half of push
-          and therefore we want to wait until the second half before looking
-          for a response
         * rebt obviously doesn't get a response
         * uninstall performs a reboot to ensure starting in a clean state and
           so also doesn't look for a response
     """
-    noResponseCmds = [re.compile('^push .*$'),
-                      re.compile('^rebt'),
+    noResponseCmds = [re.compile('^rebt'),
                       re.compile('^uninst .*$'),
                       re.compile('^pull .*$')]
 
     for c in noResponseCmds:
       if (c.match(cmd)):
         return False
 
     # If the command is not in our list, then it gets a response
@@ -114,53 +110,53 @@ class DeviceManagerSUT(DeviceManager):
                          re.compile('^uninst .*$')]
 
     for c in socketClosingCmds:
       if (c.match(cmd)):
         return True
 
     return False
 
-  def sendCmds(self, cmdlist, outputfile, timeout = None, newline = True):
+  def sendCmds(self, cmdlist, outputfile, timeout = None):
     '''
     a wrapper for _doCmds that loops up to self.retrylimit iterations.
     this allows us to move the retry logic outside of the _doCmds() to make it
     easier for debugging in the future.
     note that since cmdlist is a list of commands, they will all be retried if
     one fails.  this is necessary in particular for pushFile(), where we don't want
     to accidentally send extra data if a failure occurs during data transmission.
     '''
     done = False
     while self.retries < self.retrylimit:
       try:
-        self._doCmds(cmdlist, outputfile, timeout, newline)
+        self._doCmds(cmdlist, outputfile, timeout)
         return
       except AgentError, err:
         # re-raise error if it's fatal (i.e. the device got the command but
         # couldn't execute it). retry otherwise
         if err.fatal:
           raise err
         if self.debug >= 2:
           print err
         self.retries += 1
 
     raise AgentError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
 
-  def runCmds(self, cmdlist, timeout = None, newline = True):
+  def runCmds(self, cmdlist, timeout = None):
     '''
     similar to sendCmds, but just returns any output as a string instead of
     writing to a file. this is normally what you want to call to send a set
     of commands to the agent
     '''
     outputfile = StringIO.StringIO()
-    self.sendCmds(cmdlist, outputfile, timeout, newline)
+    self.sendCmds(cmdlist, outputfile, timeout)
     outputfile.seek(0)
     return outputfile.read()
 
-  def _doCmds(self, cmdlist, outputfile, timeout, newline):
+  def _doCmds(self, cmdlist, outputfile, timeout):
     promptre = re.compile(self.prompt_regex + '$')
     shouldCloseSocket = False
     recvGuard = 1000
 
     if not self._sock:
       try:
         if self.debug >= 1:
           print "reconnecting socket"
@@ -173,59 +169,67 @@ class DeviceManagerSUT(DeviceManager):
         self._sock.connect((self.host, int(self.port)))
         self._sock.recv(1024)
       except socket.error, msg:
         self._sock.close()
         self._sock = None
         raise AgentError("unable to connect socket: "+str(msg))
 
     for cmd in cmdlist:
-      if newline: cmd += '\r\n'
+      cmdline = '%s\r\n' % cmd['cmd']
 
       try:
-        numbytes = self._sock.send(cmd)
-        if (numbytes != len(cmd)):
-          raise AgentError("ERROR: our cmd was %s bytes and we only sent %s" % (len(cmd),
-                                                                                numbytes))
-        if (self.debug >= 4): print "send cmd: " + str(cmd)
+        sent = self._sock.send(cmdline)
+        if sent != len(cmdline):
+          raise AgentError("ERROR: our cmd was %s bytes and we "
+                           "only sent %s" % (len(cmdline), sent))
+        if cmd.get('data'):
+          sent = self._sock.send(cmd['data'])
+          if sent != len(cmd['data']):
+              raise AgentError("ERROR: we had %s bytes of data to send, but "
+                               "only sent %s" % (len(cmd['data'], sent)))
+
+        if (self.debug >= 4): print "sent cmd: " + str(cmd['cmd'])
       except socket.error, msg:
         self._sock.close()
         self._sock = None
         if self.debug >= 1:
-          print "Error sending data to socket. cmd="+str(cmd)+"; err="+str(msg)
+          print "Error sending data to socket. cmd="+str(cmd['cmd'])+"; err="+str(msg)
         return False
 
       # Check if the command should close the socket
-      shouldCloseSocket = self._shouldCmdCloseSocket(cmd)
+      shouldCloseSocket = self._shouldCmdCloseSocket(cmd['cmd'])
 
       # Handle responses from commands
-      if (self._cmdNeedsResponse(cmd)):
+      if (self._cmdNeedsResponse(cmd['cmd'])):
         found = False
         loopguard = 0
         data = ""
 
         while (found == False and (loopguard < recvGuard)):
           temp = ''
           if (self.debug >= 4): print "recv'ing..."
 
           # Get our response
           try:
             temp = self._sock.recv(1024)
             if (self.debug >= 4): print "response: " + str(temp)
           except socket.error, msg:
             self._sock.close()
             self._sock = None
-            raise AgentError("Error receiving data from socket. cmd="+str(cmd)+"; err="+str(msg))
+            raise AgentError("Error receiving data from socket. cmd="+str(cmd['cmd'])+"; err="+str(msg))
 
           data += temp
 
           # If something goes wrong in the agent it will send back a string that
-          # starts with '##AGENT-ERROR##'
-          if self.agentErrorRE.match(data):
-            raise AgentError("Agent Error processing command: %s" % cmd, fatal=True)
+          # starts with '##AGENT-WARNING##'
+          errorMatch = self.agentErrorRE.match(data)
+          if errorMatch:
+            raise AgentError("Agent Error processing command '%s'; err='%s'" %
+                             (cmd['cmd'], errorMatch.group(1)), fatal=True)
 
           for line in data.splitlines():
             if promptre.match(line):
               found = True
               data = self._stripPrompt(data)
               break
 
           # periodically flush data to output file to make sure it doesn't get
@@ -256,19 +260,19 @@ class DeviceManagerSUT(DeviceManager):
   # failure: None
   def shell(self, cmd, outputfile, env=None, cwd=None):
     cmdline = subprocess.list2cmdline(cmd)
     if env:
       cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
 
     try:
       if cwd:
-        self.sendCmds(['execcwd %s %s' % (cwd, cmdline)], outputfile)
+        self.sendCmds([{ 'cmd': 'execcwd %s %s' % (cwd, cmdline) }], outputfile)
       else:
-        self.sendCmds(['exec su -c "%s"' % cmdline], outputfile)
+        self.sendCmds([{ 'cmd': 'exec su -c "%s"' % cmdline }], outputfile)
     except AgentError:
       return None
 
     # dig through the output to get the return code
     lastline = _pop_last_line(outputfile)
     if lastline:
       m = re.search('return code \[([0-9]+)\]', lastline)
       if m:
@@ -301,19 +305,21 @@ class DeviceManagerSUT(DeviceManager):
     if (self.debug >= 3): print "sending: push " + destname
     
     filesize = os.path.getsize(localname)
     f = open(localname, 'rb')
     data = f.read()
     f.close()
 
     try:
-      retVal = self.runCmds(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
-    except AgentError:
-      retVal = False
+      retVal = self.runCmds([{ 'cmd': 'push ' + destname + ' ' + str(filesize),
+                               'data': data }])
+    except AgentError, e:
+      print "error pushing file: %s" % e.msg
+      return False
   
     if (self.debug >= 3): print "push returned: " + str(retVal)
 
     validated = False
     if (retVal):
       retline = retVal.strip()
       if (retline == None):
         # Then we failed to get back a hash from agent, try manual validation
@@ -338,17 +344,17 @@ class DeviceManagerSUT(DeviceManager):
   # returns:
   #  success: directory name
   #  failure: None
   def mkDir(self, name):
     if (self.dirExists(name)):
       return name
     else:
       try:
-        retVal = self.runCmds(['mkdr ' + name])
+        retVal = self.runCmds([{ 'cmd': 'mkdr ' + name }])
       except AgentError:
         retVal = None
       return retVal
 
   # make directory structure on the device
   # external function
   # returns:
   #  success: directory structure that we created
@@ -391,17 +397,17 @@ class DeviceManagerSUT(DeviceManager):
   # external function
   # returns:
   #  success: True
   #  failure: False
   def dirExists(self, dirname):
     match = ".*" + dirname.replace('^', '\^') + "$"
     dirre = re.compile(match)
     try:
-      data = self.runCmds(['cd ' + dirname, 'cwd'])
+      data = self.runCmds([ { 'cmd': 'cd ' + dirname }, { 'cmd': 'cwd' }])
     except AgentError:
       return False
 
     found = False
     for d in data.splitlines():
       if (dirre.match(d)):
         found = True
 
@@ -427,59 +433,59 @@ class DeviceManagerSUT(DeviceManager):
   # returns:
   #  success: array of filenames, ['file1', 'file2', ...]
   #  failure: []
   def listFiles(self, rootdir):
     rootdir = rootdir.rstrip('/')
     if (self.dirExists(rootdir) == False):
       return []
     try:
-      data = self.runCmds(['cd ' + rootdir, 'ls'])
+      data = self.runCmds([{ 'cmd': 'cd ' + rootdir }, { 'cmd': 'ls' }])
     except AgentError:
       return []
 
     files = filter(lambda x: x, data.splitlines())
     if len(files) == 1 and files[0] == '<empty>':
       # special case on the agent: empty directories return just the string "<empty>"
       return []
     return files
 
   # external function
   # returns:
   #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
   #  failure: None
   def removeFile(self, filename):
     if (self.debug>= 2): print "removing file: " + filename
     try:
-      retVal = self.runCmds(['rm ' + filename])
+      retVal = self.runCmds([{ 'cmd': 'rm ' + filename }])
     except AgentError:
       return None
 
     return retVal
   
   # does a recursive delete of directory on the device: rm -Rf remoteDir
   # external function
   # returns:
   #  success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
   #  failure: None
   def removeDir(self, remoteDir):
     try:
-      retVal = self.runCmds(['rmdr ' + remoteDir])
+      retVal = self.runCmds([{ 'cmd': 'rmdr ' + remoteDir }])
     except AgentError:
       return None
 
     return retVal
 
   # external function
   # returns:
   #  success: array of process tuples
   #  failure: []
   def getProcessList(self):
     try:
-      data = self.runCmds(['ps'])
+      data = self.runCmds([{ 'cmd': 'ps' }])
     except AgentError:
       return []
 
     files = []
     for line in data.splitlines():
       if line:
         pidproc = line.strip().split()
         if (len(pidproc) == 2):
@@ -502,17 +508,17 @@ class DeviceManagerSUT(DeviceManager):
     if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
 
     if (self.processExist(appname) != None):
       print "WARNING: process %s appears to be running already\n" % appname
       if (failIfRunning):
         return None
     
     try:
-      data = self.runCmds(['exec ' + appname])
+      data = self.runCmds([{ 'cmd': 'exec ' + appname }])
     except AgentError:
       return None
 
     # wait up to 30 seconds for process to start up
     timeslept = 0
     while (timeslept <= 30):
       process = self.processExist(appname)
       if (process is not None):
@@ -551,41 +557,41 @@ class DeviceManagerSUT(DeviceManager):
   # external function
   # returns:
   #  success: True
   #  failure: False
   def killProcess(self, appname, forceKill=False):
     if forceKill:
       print "WARNING: killProcess(): forceKill parameter unsupported on SUT"
     try:
-      data = self.runCmds(['kill ' + appname])
+      data = self.runCmds([{ 'cmd': 'kill ' + appname }])
     except AgentError:
       return False
 
     return True
 
   # external function
   # returns:
   #  success: tmpdir, string
   #  failure: None
   def getTempDir(self):
     try:
-      data = self.runCmds(['tmpd'])
+      data = self.runCmds([{ 'cmd': 'tmpd' }])
     except AgentError:
       return None
 
     return data.strip()
 
   # external function
   # returns:
   #  success: filecontents
   #  failure: None
   def catFile(self, remoteFile):
     try:
-      data = self.runCmds(['cat ' + remoteFile])
+      data = self.runCmds([{ 'cmd': 'cat ' + remoteFile }])
     except AgentError:
       return None
 
     return data
 
   # external function
   # returns:
   #  success: output of pullfile, string
@@ -644,17 +650,17 @@ class DeviceManagerSUT(DeviceManager):
     prompt = self.base_prompt + self.prompt_sep
     buffer = ''
     
     # expected return value:
     # <filename>,<filesize>\n<filedata>
     # or, if error,
     # <filename>,-1\n<error message>
     try:
-      data = self.runCmds(['pull ' + remoteFile])
+      data = self.runCmds([{ 'cmd': 'pull ' + remoteFile }])
     except AgentError:
       return None
 
     # read metadata; buffer the rest
     metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata')
     if not metadata:
       return None
     if self.debug >= 3:
@@ -763,17 +769,17 @@ class DeviceManagerSUT(DeviceManager):
 
   # external function
   # returns:
   #  success: True
   #  failure: False
   #  Throws a FileError exception when null (invalid dir/filename)
   def isDir(self, remotePath):
     try:
-      data = self.runCmds(['isdir ' + remotePath])
+      data = self.runCmds([{ 'cmd': 'isdir ' + remotePath }])
     except AgentError:
       # normally there should be no error here; a nonexistent file/directory will
       # return the string "<filename>: No such file or directory".
       # However, I've seen AGENT-WARNING returned before. 
       return False
 
     retVal = data.strip()
     if not retVal:
@@ -799,17 +805,17 @@ class DeviceManagerSUT(DeviceManager):
   
   # return the md5 sum of a remote file
   # internal function
   # returns:
   #  success: MD5 hash for given filename
   #  failure: None
   def getRemoteHash(self, filename):
     try:
-      data = self.runCmds(['hash ' + filename])
+      data = self.runCmds([{ 'cmd': 'hash ' + filename }])
     except AgentError:
       return None
 
     retVal = None
     if data:
       retVal = data.strip()
     if (self.debug >= 3): print "remote hash returned: '" + retVal + "'"
     return retVal
@@ -828,31 +834,31 @@ class DeviceManagerSUT(DeviceManager):
   #       /mochitest
   #
   # external function
   # returns:
   #  success: path for device root
   #  failure: None
   def getDeviceRoot(self):
     try:
-      data = self.runCmds(['testroot'])
+      data = self.runCmds([{ 'cmd': 'testroot' }])
     except:
       return None
 
     deviceRoot = data.strip() + '/tests'
 
     if (not self.dirExists(deviceRoot)):
       if (self.mkDir(deviceRoot) == None):
         return None
 
     return deviceRoot
 
   def getAppRoot(self, packageName):
     try:
-      data = self.runCmds(['getapproot '+packageName])
+      data = self.runCmds([{ 'cmd': 'getapproot '+packageName }])
     except:
       return None
 
     return data.strip()
 
   # external function
   # returns:
   #  success: output of unzip command
@@ -870,17 +876,17 @@ class DeviceManagerSUT(DeviceManager):
     elif self.fileExists('/' + filename):
       dir = '/' + filename
     elif self.fileExists(devroot + '/' + filename):
       dir = devroot + '/' + filename
     else:
       return None
 
     try:
-      data = self.runCmds(['cd ' + dir, 'unzp ' + filename])
+      data = self.runCmds([{ 'cmd': 'cd ' + dir }, { 'cmd': 'unzp ' + filename }])
     except AgentError:
       return None
 
     return data
 
   # external function
   # returns:
   #  success: status from test agent
@@ -891,27 +897,28 @@ class DeviceManagerSUT(DeviceManager):
     if (self.debug > 3): print "INFO: sending rebt command"
     callbacksvrstatus = None    
 
     if (ipAddr is not None):
     #create update.info file:
       try:
         destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info'
         data = "%s,%s\rrebooting\r" % (ipAddr, port)
-        self.runCmds(['push ' + destname + ' ' + str(len(data)) + '\r\n', data], newline = False)
+        self.runCmds([{ 'cmd': 'push %s %s' % (destname, len(data)),
+                        'data': data }])
       except AgentError:
         return None
 
       ip, port = self.getCallbackIpAndPort(ipAddr, port)
       cmd += " %s %s" % (ip, port)
       # Set up our callback server
       callbacksvr = callbackServer(ip, port, self.debug)
 
     try:
-      status = self.runCmds([cmd])
+      status = self.runCmds([{ 'cmd': cmd }])
     except AgentError:
       return None
 
     if (ipAddr is not None):
       status = callbacksvr.disconnect()
 
     if (self.debug > 3): print "INFO: rebt- got status back: " + str(status)
     return status
@@ -938,17 +945,17 @@ class DeviceManagerSUT(DeviceManager):
     collapseSpaces = re.compile('  +')
 
     directives = ['os','id','uptime','systime','screen','rotation','memory','process',
                   'disk','power']
     if (directive in directives):
       directives = [directive]
 
     for d in directives:
-      data = self.runCmds(['info ' + d])
+      data = self.runCmds([{ 'cmd': 'info ' + d }])
       if (data is None):
         continue
       data = collapseSpaces.sub(' ', data)
       result[d] = data.split('\n')
 
     # Get rid of any 0 length members of the arrays
     for k, v in result.iteritems():
       result[k] = filter(lambda x: x != '', result[k])
@@ -975,17 +982,17 @@ class DeviceManagerSUT(DeviceManager):
   # returns:
   #  success: output from agent for inst command
   #  failure: None
   def installApp(self, appBundlePath, destPath=None):
     cmd = 'inst ' + appBundlePath
     if destPath:
       cmd += ' ' + destPath
     try:
-      data = self.runCmds([cmd])
+      data = self.runCmds([{ 'cmd': cmd }])
     except AgentError:
       return None
 
     f = re.compile('Failure')
     for line in data.split():
       if (f.match(line)):
         return data
     return None
@@ -1001,17 +1008,17 @@ class DeviceManagerSUT(DeviceManager):
   # returns:
   #  success: True
   #  failure: None
   def uninstallAppAndReboot(self, appName, installPath=None):
     cmd = 'uninst ' + appName
     if installPath:
       cmd += ' ' + installPath
     try:
-      data = self.runCmds([cmd])
+      data = self.runCmds([{ 'cmd': cmd }])
     except AgentError:
       return None
 
     if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
     return True
 
   """
   Updates the application on the device.
@@ -1046,17 +1053,17 @@ class DeviceManagerSUT(DeviceManager):
       ip, port = self.getCallbackIpAndPort(ipAddr, port)
       cmd += " %s %s" % (ip, port)
       # Set up our callback server
       callbacksvr = callbackServer(ip, port, self.debug)
 
     if (self.debug >= 3): print "INFO: updateApp using command: " + str(cmd)
 
     try:
-      status = self.runCmds([cmd])
+      status = self.runCmds([{ 'cmd': cmd }])
     except AgentError:
       return None
 
     if ipAddr is not None:
       status = callbacksvr.disconnect()
 
     if (self.debug >= 3): print "INFO: updateApp: got status back: " + str(status)
 
@@ -1066,17 +1073,17 @@ class DeviceManagerSUT(DeviceManager):
     return the current time on the device
   """
   # external function
   # returns:
   #  success: time in ms
   #  failure: None
   def getCurrentTime(self):
     try:
-      data = self.runCmds(['clok'])
+      data = self.runCmds([{ 'cmd': 'clok' }])
     except AgentError:
       return None
 
     return data.strip()
 
   """
     Connect the ipaddress and port for a callback ping.  Defaults to current IP address
     And ports starting at 30000.
@@ -1170,30 +1177,30 @@ class DeviceManagerSUT(DeviceManager):
     if (width < 100 or width > 9999):
       return False
 
     if (height < 100 or height > 9999):
       return False
 
     if (self.debug >= 3): print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height)
     try:
-      self.runCmds(["exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width)])
-      self.runCmds(["exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height)])
+      self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width) }])
+      self.runCmds([{ 'cmd': "exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height) }])
     except AgentError:
       return False
 
     return True
 
   # external function
   # returns:
   #  success: True
   #  failure: False
   def chmodDir(self, remoteDir):
     try:
-      self.runCmds(["chmod "+remoteDir])
+      self.runCmds([{ 'cmd': "chmod "+remoteDir }])
     except AgentError:
       return False
     return True
 
 gCallbackData = ''
 
 class myServer(SocketServer.TCPServer):
   allow_reuse_address = True