зеркало из https://github.com/mozilla/gecko-dev.git
Bug 728298 - DeviceManager needs a good, standard way of starting an Android application. r=jmaher
This commit is contained in:
Родитель
0ca6d51457
Коммит
f9f8f417d0
|
@ -61,7 +61,6 @@ class DMError(Exception):
|
|||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
|
||||
def abstractmethod(method):
|
||||
line = method.func_code.co_firstlineno
|
||||
filename = method.func_code.co_filename
|
||||
|
@ -70,9 +69,18 @@ def abstractmethod(method):
|
|||
'should be implemented by a concrete class' %
|
||||
(repr(method), filename,line))
|
||||
return not_implemented
|
||||
|
||||
|
||||
class DeviceManager:
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def shell(self, cmd, outputfile, env=None, cwd=None):
|
||||
"""
|
||||
executes shell command on device
|
||||
returns:
|
||||
success: Return code from command
|
||||
failure: None
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def pushFile(self, localname, destname):
|
||||
"""
|
||||
|
@ -168,25 +176,27 @@ class DeviceManager:
|
|||
success: array of process tuples
|
||||
failure: None
|
||||
"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def fireProcess(self, appname, failIfRunning=False):
|
||||
"""
|
||||
external function
|
||||
DEPRECATED: Use shell() or launchApplication() for new code
|
||||
returns:
|
||||
success: pid
|
||||
failure: None
|
||||
"""
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
|
||||
"""
|
||||
external function
|
||||
DEPRECATED: Use shell() or launchApplication() for new code
|
||||
returns:
|
||||
success: output filename
|
||||
failure: None
|
||||
"""
|
||||
|
||||
|
||||
def communicate(self, process, timeout = 600, interval = 5):
|
||||
"""
|
||||
loops until 'process' has exited or 'timeout' seconds is reached
|
||||
|
@ -581,3 +591,35 @@ class NetworkTools:
|
|||
print "Socket error trying to find open port"
|
||||
|
||||
return seed
|
||||
|
||||
def _pop_last_line(file):
|
||||
'''
|
||||
Utility function to get the last line from a file (shared between ADB and
|
||||
SUT device managers). Function also removes it from the file. Intended to
|
||||
strip off the return code from a shell command.
|
||||
'''
|
||||
bytes_from_end = 1
|
||||
file.seek(0, 2)
|
||||
length = file.tell() + 1
|
||||
while bytes_from_end <= length:
|
||||
file.seek((-1)*bytes_from_end, 2)
|
||||
data = file.read()
|
||||
|
||||
if bytes_from_end == length and len(data) == 0: # no data, return None
|
||||
return None
|
||||
|
||||
if data[0] == '\n' or bytes_from_end == length:
|
||||
# found the last line, which should have the return value
|
||||
if data[0] == '\n':
|
||||
data = data[1:]
|
||||
|
||||
# truncate off the return code line
|
||||
file.truncate(length - bytes_from_end)
|
||||
file.seek(0,2)
|
||||
file.write('\0')
|
||||
|
||||
return data
|
||||
|
||||
bytes_from_end += 1
|
||||
|
||||
return None
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import subprocess
|
||||
from devicemanager import DeviceManager, DMError
|
||||
from devicemanager import DeviceManager, DMError, _pop_last_line
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
@ -63,6 +63,47 @@ class DeviceManagerADB(DeviceManager):
|
|||
else:
|
||||
print "restarting as root failed"
|
||||
|
||||
# external function: executes shell command on device
|
||||
# returns:
|
||||
# success: <return code>
|
||||
# failure: None
|
||||
def shell(self, cmd, outputfile, env=None, cwd=None):
|
||||
# need to quote special characters here
|
||||
for (index, arg) in enumerate(cmd):
|
||||
if arg.find(" ") or arg.find("(") or arg.find(")") or arg.find("\""):
|
||||
cmd[index] = '\'%s\'' % arg
|
||||
|
||||
# This is more complex than you'd think because adb doesn't actually
|
||||
# return the return code from a process, so we have to capture the output
|
||||
# to get it
|
||||
# FIXME: this function buffers all output of the command into memory,
|
||||
# always. :(
|
||||
cmdline = subprocess.list2cmdline(cmd) + "; echo $?"
|
||||
|
||||
# prepend cwd and env to command if necessary
|
||||
if cwd:
|
||||
cmdline = "cd %s; %s" % (cwd, cmdline)
|
||||
if env:
|
||||
envstr = '; '.join(map(lambda x: 'export %s=%s' % (x[0], x[1]), env.iteritems()))
|
||||
cmdline = envstr + "; " + cmdline
|
||||
|
||||
# all output should be in stdout
|
||||
proc = subprocess.Popen(["adb", "shell", cmdline],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = proc.communicate()
|
||||
outputfile.write(stdout.rstrip('\n'))
|
||||
|
||||
lastline = _pop_last_line(outputfile)
|
||||
if lastline:
|
||||
m = re.search('([0-9]+)', lastline)
|
||||
if m:
|
||||
return_code = m.group(1)
|
||||
outputfile.seek(-2, 2)
|
||||
outputfile.truncate() # truncate off the return code
|
||||
return return_code
|
||||
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: True
|
||||
|
@ -264,6 +305,7 @@ class DeviceManagerADB(DeviceManager):
|
|||
return ret
|
||||
|
||||
# external function
|
||||
# DEPRECATED: Use shell() or launchApplication() for new code
|
||||
# returns:
|
||||
# success: pid
|
||||
# failure: None
|
||||
|
@ -275,6 +317,7 @@ class DeviceManagerADB(DeviceManager):
|
|||
return self.launchProcess(parts, failIfRunning)
|
||||
|
||||
# external function
|
||||
# DEPRECATED: Use shell() or launchApplication() for new code
|
||||
# returns:
|
||||
# success: output filename
|
||||
# failure: None
|
||||
|
@ -327,6 +370,7 @@ class DeviceManagerADB(DeviceManager):
|
|||
if name == appname:
|
||||
p = self.runCmdAs(["shell", "kill", pid])
|
||||
return p.stdout.read()
|
||||
|
||||
return None
|
||||
|
||||
# external function
|
||||
|
@ -611,7 +655,7 @@ class DeviceManagerADB(DeviceManager):
|
|||
args.insert(1, "run-as")
|
||||
args.insert(2, self.packageName)
|
||||
args.insert(0, "adb")
|
||||
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
return subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
|
||||
def runCmdAs(self, args):
|
||||
if self.useRunAs:
|
||||
|
|
|
@ -47,7 +47,18 @@ import subprocess
|
|||
from threading import Thread
|
||||
import traceback
|
||||
import sys
|
||||
from devicemanager import DeviceManager, DMError, FileError, NetworkTools
|
||||
import StringIO
|
||||
from devicemanager import DeviceManager, DMError, FileError, NetworkTools, _pop_last_line
|
||||
|
||||
class AgentError(Exception):
|
||||
"SUTAgent-specific exception."
|
||||
|
||||
def __init__(self, msg= '', fatal = False):
|
||||
self.msg = msg
|
||||
self.fatal = fatal
|
||||
|
||||
def __str__(self):
|
||||
return self.msg
|
||||
|
||||
class DeviceManagerSUT(DeviceManager):
|
||||
host = ''
|
||||
|
@ -76,7 +87,7 @@ class DeviceManagerSUT(DeviceManager):
|
|||
self._sock = None
|
||||
self.getDeviceRoot()
|
||||
|
||||
def cmdNeedsResponse(self, cmd):
|
||||
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
|
||||
|
@ -93,18 +104,44 @@ class DeviceManagerSUT(DeviceManager):
|
|||
for c in noResponseCmds:
|
||||
if (c.match(cmd)):
|
||||
return False
|
||||
|
||||
|
||||
# If the command is not in our list, then it gets a response
|
||||
return True
|
||||
|
||||
def shouldCmdCloseSocket(self, cmd):
|
||||
def _stripPrompt(self, data):
|
||||
'''
|
||||
internal function
|
||||
take a data blob and strip instances of the prompt '$>\x00'
|
||||
'''
|
||||
promptre = re.compile(self.prompt_regex + '.*')
|
||||
retVal = []
|
||||
lines = data.split('\n')
|
||||
for line in lines:
|
||||
foundPrompt = False
|
||||
try:
|
||||
while (promptre.match(line)):
|
||||
foundPrompt = True
|
||||
pieces = line.split(self.prompt_sep)
|
||||
index = pieces.index('$>')
|
||||
pieces.pop(index)
|
||||
line = self.prompt_sep.join(pieces)
|
||||
except(ValueError):
|
||||
pass
|
||||
|
||||
# we don't want to append lines that are blank after stripping the
|
||||
# prompt (those are basically "prompts")
|
||||
if not foundPrompt or line:
|
||||
retVal.append(line)
|
||||
|
||||
return '\n'.join(retVal)
|
||||
|
||||
def _shouldCmdCloseSocket(self, cmd):
|
||||
""" Some commands need to close the socket after they are sent:
|
||||
* push
|
||||
* rebt
|
||||
* uninst
|
||||
* quit
|
||||
"""
|
||||
|
||||
socketClosingCmds = [re.compile('^push .*$'),
|
||||
re.compile('^quit.*'),
|
||||
re.compile('^rebt.*'),
|
||||
|
@ -116,85 +153,86 @@ class DeviceManagerSUT(DeviceManager):
|
|||
|
||||
return False
|
||||
|
||||
# convenience function to enable checks for agent errors
|
||||
def verifySendCMD(self, cmdline, newline = True):
|
||||
return self.sendCMD(cmdline, newline, False)
|
||||
|
||||
|
||||
#
|
||||
# create a wrapper for sendCMD that loops up to self.retrylimit iterations.
|
||||
# this allows us to move the retry logic outside of the _doCMD() to make it
|
||||
# easier for debugging in the future.
|
||||
# note that since cmdline 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.
|
||||
#
|
||||
def sendCMD(self, cmdline, newline = True, ignoreAgentErrors = True):
|
||||
def sendCmds(self, cmdlist, outputfile, timeout = None, newline = True):
|
||||
'''
|
||||
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 (not done):
|
||||
retVal = self._doCMD(cmdline, newline)
|
||||
if (retVal is None):
|
||||
while self.retries < self.retrylimit:
|
||||
try:
|
||||
self._doCmds(cmdlist, outputfile, timeout, newline)
|
||||
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
|
||||
else:
|
||||
self.retries = 0
|
||||
if ignoreAgentErrors == False:
|
||||
if (self.agentErrorRE.match(retVal)):
|
||||
raise DMError("error on the agent executing '%s'" % cmdline)
|
||||
return retVal
|
||||
|
||||
if (self.retries >= self.retrylimit):
|
||||
done = True
|
||||
raise AgentError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
|
||||
|
||||
raise DMError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
|
||||
def runCmds(self, cmdlist, timeout = None, newline = True):
|
||||
'''
|
||||
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)
|
||||
outputfile.seek(0)
|
||||
return outputfile.read()
|
||||
|
||||
def _doCMD(self, cmdline, newline = True):
|
||||
def _doCmds(self, cmdlist, outputfile, timeout, newline):
|
||||
promptre = re.compile(self.prompt_regex + '$')
|
||||
data = ""
|
||||
shouldCloseSocket = False
|
||||
recvGuard = 1000
|
||||
|
||||
if (self._sock == None):
|
||||
if not self._sock:
|
||||
try:
|
||||
if (self.debug >= 1):
|
||||
if self.debug >= 1:
|
||||
print "reconnecting socket"
|
||||
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
except:
|
||||
self._sock = None
|
||||
if (self.debug >= 2):
|
||||
print "unable to create socket"
|
||||
return None
|
||||
|
||||
raise AgentError("unable to create socket")
|
||||
|
||||
try:
|
||||
self._sock.connect((self.host, int(self.port)))
|
||||
self._sock.recv(1024)
|
||||
except:
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
if (self.debug >= 2):
|
||||
print "unable to connect socket"
|
||||
return None
|
||||
|
||||
for cmd in cmdline:
|
||||
raise AgentError("unable to connect socket")
|
||||
|
||||
for cmd in cmdlist:
|
||||
if newline: cmd += '\r\n'
|
||||
|
||||
|
||||
try:
|
||||
numbytes = self._sock.send(cmd)
|
||||
if (numbytes != len(cmd)):
|
||||
print "ERROR: our cmd was " + str(len(cmd)) + " bytes and we only sent " + str(numbytes)
|
||||
return None
|
||||
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)
|
||||
except:
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
return None
|
||||
|
||||
return False
|
||||
|
||||
# Check if the command should close the socket
|
||||
shouldCloseSocket = self.shouldCmdCloseSocket(cmd)
|
||||
shouldCloseSocket = self._shouldCmdCloseSocket(cmd)
|
||||
|
||||
# Handle responses from commands
|
||||
if (self.cmdNeedsResponse(cmd)):
|
||||
if (self._cmdNeedsResponse(cmd)):
|
||||
found = False
|
||||
loopguard = 0
|
||||
data = ""
|
||||
|
||||
while (found == False and (loopguard < recvGuard)):
|
||||
temp = ''
|
||||
|
@ -207,14 +245,14 @@ class DeviceManagerSUT(DeviceManager):
|
|||
except:
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
return None
|
||||
raise AgentError("Error receiving data from socket")
|
||||
|
||||
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):
|
||||
break
|
||||
raise AgentError("Agent Error processing command: %s" % cmd, fatal=True)
|
||||
|
||||
for line in data.splitlines():
|
||||
if promptre.match(line):
|
||||
|
@ -222,40 +260,54 @@ class DeviceManagerSUT(DeviceManager):
|
|||
data = self._stripPrompt(data)
|
||||
break
|
||||
|
||||
# periodically flush data to output file to make sure it doesn't get
|
||||
# too big/unwieldly
|
||||
if len(data) > 1024:
|
||||
outputfile.write(data[0:1024])
|
||||
data = data[1024:]
|
||||
|
||||
# If we violently lose the connection to the device, this loop tends to spin,
|
||||
# this guard prevents that
|
||||
if (temp == ''):
|
||||
loopguard += 1
|
||||
|
||||
if (shouldCloseSocket == True):
|
||||
# Write any remaining data to outputfile
|
||||
outputfile.write(data)
|
||||
|
||||
if shouldCloseSocket:
|
||||
try:
|
||||
self._sock.close()
|
||||
self._sock = None
|
||||
except:
|
||||
self._sock = None
|
||||
return None
|
||||
raise AgentError("Error closing socket")
|
||||
|
||||
return data
|
||||
|
||||
# internal function
|
||||
# take a data blob and strip instances of the prompt '$>\x00'
|
||||
def _stripPrompt(self, data):
|
||||
promptre = re.compile(self.prompt_regex + '.*')
|
||||
retVal = []
|
||||
lines = data.split('\n')
|
||||
for line in lines:
|
||||
try:
|
||||
while (promptre.match(line)):
|
||||
pieces = line.split(self.prompt_sep)
|
||||
index = pieces.index('$>')
|
||||
pieces.pop(index)
|
||||
line = self.prompt_sep.join(pieces)
|
||||
except(ValueError):
|
||||
pass
|
||||
retVal.append(line)
|
||||
# external function: executes shell command on device
|
||||
# returns:
|
||||
# success: <return code>
|
||||
# failure: None
|
||||
def shell(self, cmd, outputfile, env=None, cwd=None):
|
||||
cmdline = subprocess.list2cmdline(cmd)
|
||||
if env:
|
||||
cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
|
||||
|
||||
return '\n'.join(retVal)
|
||||
|
||||
try:
|
||||
if cwd:
|
||||
self.sendCmds(['execcwd %s %s' % (cwd, cmdline)], outputfile)
|
||||
else:
|
||||
self.sendCmds(['exec %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:
|
||||
return m.group(1)
|
||||
|
||||
# woops, we couldn't find an end of line/return value
|
||||
return None
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
|
@ -286,8 +338,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
f.close()
|
||||
|
||||
try:
|
||||
retVal = self.verifySendCMD(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
|
||||
except(DMError):
|
||||
retVal = self.runCmds(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
|
||||
except AgentError:
|
||||
retVal = False
|
||||
|
||||
if (self.debug >= 3): print "push returned: " + str(retVal)
|
||||
|
@ -323,8 +375,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
return name
|
||||
else:
|
||||
try:
|
||||
retVal = self.verifySendCMD(['mkdr ' + name])
|
||||
except(DMError):
|
||||
retVal = self.runCmds(['mkdr ' + name])
|
||||
except AgentError:
|
||||
retVal = None
|
||||
return retVal
|
||||
|
||||
|
@ -376,8 +428,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
match = ".*" + dirname + "$"
|
||||
dirre = re.compile(match)
|
||||
try:
|
||||
data = self.verifySendCMD(['cd ' + dirname, 'cwd'])
|
||||
except(DMError):
|
||||
data = self.runCmds(['cd ' + dirname, 'cwd'])
|
||||
except AgentError:
|
||||
return False
|
||||
|
||||
found = False
|
||||
|
@ -412,8 +464,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
if (self.dirExists(rootdir) == False):
|
||||
return []
|
||||
try:
|
||||
data = self.verifySendCMD(['cd ' + rootdir, 'ls'])
|
||||
except(DMError):
|
||||
data = self.runCmds(['cd ' + rootdir, 'ls'])
|
||||
except AgentError:
|
||||
return []
|
||||
|
||||
files = filter(lambda x: x, data.splitlines())
|
||||
|
@ -429,8 +481,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
def removeFile(self, filename):
|
||||
if (self.debug>= 2): print "removing file: " + filename
|
||||
try:
|
||||
retVal = self.verifySendCMD(['rm ' + filename])
|
||||
except(DMError):
|
||||
retVal = self.runCmds(['rm ' + filename])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return retVal
|
||||
|
@ -442,8 +494,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def removeDir(self, remoteDir):
|
||||
try:
|
||||
retVal = self.verifySendCMD(['rmdr ' + remoteDir])
|
||||
except(DMError):
|
||||
retVal = self.runCmds(['rmdr ' + remoteDir])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return retVal
|
||||
|
@ -454,8 +506,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: []
|
||||
def getProcessList(self):
|
||||
try:
|
||||
data = self.verifySendCMD(['ps'])
|
||||
except DMError:
|
||||
data = self.runCmds(['ps'])
|
||||
except AgentError:
|
||||
return []
|
||||
|
||||
files = []
|
||||
|
@ -470,6 +522,7 @@ class DeviceManagerSUT(DeviceManager):
|
|||
return files
|
||||
|
||||
# external function
|
||||
# DEPRECATED: Use shell() or launchApplication() for new code
|
||||
# returns:
|
||||
# success: pid
|
||||
# failure: None
|
||||
|
@ -486,8 +539,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
return None
|
||||
|
||||
try:
|
||||
data = self.verifySendCMD(['exec ' + appname])
|
||||
except(DMError):
|
||||
data = self.runCmds(['exec ' + appname])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
# wait up to 30 seconds for process to start up
|
||||
|
@ -503,6 +556,7 @@ class DeviceManagerSUT(DeviceManager):
|
|||
return process
|
||||
|
||||
# external function
|
||||
# DEPRECATED: Use shell() or launchApplication() for new code
|
||||
# returns:
|
||||
# success: output filename
|
||||
# failure: None
|
||||
|
@ -532,8 +586,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def killProcess(self, appname):
|
||||
try:
|
||||
data = self.verifySendCMD(['kill ' + appname])
|
||||
except(DMError):
|
||||
data = self.runCmds(['kill ' + appname])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return data
|
||||
|
@ -544,8 +598,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def getTempDir(self):
|
||||
try:
|
||||
data = self.verifySendCMD(['tmpd'])
|
||||
except(DMError):
|
||||
data = self.runCmds(['tmpd'])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return data.strip()
|
||||
|
@ -556,12 +610,12 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def catFile(self, remoteFile):
|
||||
try:
|
||||
data = self.verifySendCMD(['cat ' + remoteFile])
|
||||
except(DMError):
|
||||
data = self.runCmds(['cat ' + remoteFile])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
# external function
|
||||
# returns:
|
||||
# success: output of pullfile, string
|
||||
|
@ -625,8 +679,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# or, if error,
|
||||
# <filename>,-1\n<error message>
|
||||
try:
|
||||
data = self.verifySendCMD(['pull ' + remoteFile])
|
||||
except(DMError):
|
||||
data = self.runCmds(['pull ' + remoteFile])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
# read metadata; buffer the rest
|
||||
|
@ -744,8 +798,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# Throws a FileError exception when null (invalid dir/filename)
|
||||
def isDir(self, remotePath):
|
||||
try:
|
||||
data = self.verifySendCMD(['isdir ' + remotePath])
|
||||
except(DMError):
|
||||
data = self.runCmds(['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.
|
||||
|
@ -780,8 +834,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def getRemoteHash(self, filename):
|
||||
try:
|
||||
data = self.verifySendCMD(['hash ' + filename])
|
||||
except(DMError):
|
||||
data = self.runCmds(['hash ' + filename])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
retVal = None
|
||||
|
@ -809,7 +863,7 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def getDeviceRoot(self):
|
||||
try:
|
||||
data = self.verifySendCMD(['testroot'])
|
||||
data = self.runCmds(['testroot'])
|
||||
except:
|
||||
return None
|
||||
|
||||
|
@ -823,7 +877,7 @@ class DeviceManagerSUT(DeviceManager):
|
|||
|
||||
def getAppRoot(self, packageName):
|
||||
try:
|
||||
data = self.verifySendCMD(['getapproot '+packageName])
|
||||
data = self.runCmds(['getapproot '+packageName])
|
||||
except:
|
||||
return None
|
||||
|
||||
|
@ -851,8 +905,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
return None
|
||||
|
||||
try:
|
||||
data = self.verifySendCMD(['cd ' + dir, 'unzp ' + filename])
|
||||
except(DMError):
|
||||
data = self.runCmds(['cd ' + dir, 'unzp ' + filename])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return data
|
||||
|
@ -872,8 +926,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
try:
|
||||
destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info'
|
||||
data = "%s,%s\rrebooting\r" % (ipAddr, port)
|
||||
self.verifySendCMD(['push ' + destname + ' ' + str(len(data)) + '\r\n', data], newline = False)
|
||||
except(DMError):
|
||||
self.runCmds(['push ' + destname + ' ' + str(len(data)) + '\r\n', data], newline = False)
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
ip, port = self.getCallbackIpAndPort(ipAddr, port)
|
||||
|
@ -882,8 +936,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
callbacksvr = callbackServer(ip, port, self.debug)
|
||||
|
||||
try:
|
||||
status = self.verifySendCMD([cmd])
|
||||
except(DMError):
|
||||
status = self.runCmds([cmd])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
if (ipAddr is not None):
|
||||
|
@ -918,7 +972,7 @@ class DeviceManagerSUT(DeviceManager):
|
|||
directives = [directive]
|
||||
|
||||
for d in directives:
|
||||
data = self.verifySendCMD(['info ' + d])
|
||||
data = self.runCmds(['info ' + d])
|
||||
if (data is None):
|
||||
continue
|
||||
data = collapseSpaces.sub(' ', data)
|
||||
|
@ -955,8 +1009,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
if destPath:
|
||||
cmd += ' ' + destPath
|
||||
try:
|
||||
data = self.verifySendCMD([cmd])
|
||||
except(DMError):
|
||||
data = self.runCmds([cmd])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
f = re.compile('Failure')
|
||||
|
@ -981,8 +1035,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
if installPath:
|
||||
cmd += ' ' + installPath
|
||||
try:
|
||||
data = self.verifySendCMD([cmd])
|
||||
except(DMError):
|
||||
data = self.runCmds([cmd])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
|
||||
|
@ -1026,8 +1080,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
if (self.debug >= 3): print "INFO: updateApp using command: " + str(cmd)
|
||||
|
||||
try:
|
||||
status = self.verifySendCMD([cmd])
|
||||
except(DMError):
|
||||
status = self.runCmds([cmd])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
if ipAddr is not None:
|
||||
|
@ -1046,8 +1100,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
# failure: None
|
||||
def getCurrentTime(self):
|
||||
try:
|
||||
data = self.verifySendCMD(['clok'])
|
||||
except(DMError):
|
||||
data = self.runCmds(['clok'])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return data.strip()
|
||||
|
@ -1079,8 +1133,8 @@ class DeviceManagerSUT(DeviceManager):
|
|||
return None
|
||||
|
||||
try:
|
||||
data = self.verifySendCMD(['cd ' + dir, 'unzp ' + filename])
|
||||
except(DMError):
|
||||
data = self.runCmds(['cd ' + dir, 'unzp ' + filename])
|
||||
except AgentError:
|
||||
return None
|
||||
|
||||
return data
|
||||
|
@ -1150,9 +1204,9 @@ class DeviceManagerSUT(DeviceManager):
|
|||
|
||||
if (self.debug >= 3): print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height)
|
||||
try:
|
||||
self.verifySendCMD(["exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width)])
|
||||
self.verifySendCMD(["exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height)])
|
||||
except(DMError):
|
||||
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)])
|
||||
except AgentError:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
# ***** BEGIN LICENSE BLOCK *****
|
||||
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public License Version
|
||||
# 1.1 (the "License"); you may not use this file except in compliance with
|
||||
# the License. You may obtain a copy of the License at
|
||||
# http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS IS" basis,
|
||||
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
# for the specific language governing rights and limitations under the
|
||||
# License.
|
||||
#
|
||||
# The Original Code is Test Automation Framework.
|
||||
#
|
||||
# The Initial Developer of the Original Code is
|
||||
# Mozilla foundation
|
||||
#
|
||||
# Portions created by the Initial Developer are Copyright (C) 2009
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Joel Maher <joel.maher@gmail.com> (Original Developer)
|
||||
# William Lachance <wlachance@mozilla.com>
|
||||
#
|
||||
# Alternatively, the contents of this file may be used under the terms of
|
||||
# either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
# in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
# of those above. If you wish to allow use of your version of this file only
|
||||
# under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
# use your version of this file under the terms of the MPL, indicate your
|
||||
# decision by deleting the provisions above and replace them with the notice
|
||||
# and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
# the provisions above, a recipient may use your version of this file under
|
||||
# the terms of any one of the MPL, the GPL or the LGPL.
|
||||
#
|
||||
# ***** END LICENSE BLOCK *****
|
||||
|
||||
from devicemanagerADB import DeviceManagerADB
|
||||
from devicemanagerSUT import DeviceManagerSUT
|
||||
|
||||
class DroidMixin(object):
|
||||
"""Mixin to extend DeviceManager with Android-specific functionality"""
|
||||
|
||||
def launchApplication(self, app, activity="App",
|
||||
intent="android.intent.action.VIEW", env=None,
|
||||
url=None, extra_args=None):
|
||||
"""
|
||||
Launches an Android application
|
||||
returns:
|
||||
success: True
|
||||
failure: False
|
||||
"""
|
||||
# only one instance of an application may be running at once
|
||||
if self.processExist(app):
|
||||
return False
|
||||
|
||||
acmd = [ "am", "start", "-a", intent, "-W", "-n", "%s/.%s" % (app, activity)]
|
||||
|
||||
if extra_args:
|
||||
acmd.extend(["--es", "args", " ".join(args)])
|
||||
|
||||
if env:
|
||||
envCnt = 0
|
||||
# env is expected to be a dict of environment variables
|
||||
for envkey, envval in env.iteritems():
|
||||
acmd.extend(["--es", "env" + str(envCnt), envkey + "=" + envval])
|
||||
envCnt += 1
|
||||
|
||||
if url:
|
||||
acmd.extend(["-d", ''.join(['"', url, '"'])])
|
||||
|
||||
# shell output not that interesting and debugging logs should already
|
||||
# show what's going on here... so just create an empty memory buffer
|
||||
# and ignore
|
||||
shellOutput = StringIO.StringIO()
|
||||
if self.shell(acmd, shellOutput) == 0:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
class DroidADB(DeviceManagerADB, DroidMixin):
|
||||
pass
|
||||
|
||||
class DroidSUT(DeviceManagerSUT, DroidMixin):
|
||||
pass
|
|
@ -136,12 +136,13 @@ public class DoCommand {
|
|||
String ffxProvider = "org.mozilla.ffxcp";
|
||||
String fenProvider = "org.mozilla.fencp";
|
||||
|
||||
private final String prgVersion = "SUTAgentAndroid Version 1.05";
|
||||
private final String prgVersion = "SUTAgentAndroid Version 1.06";
|
||||
|
||||
public enum Command
|
||||
{
|
||||
RUN ("run"),
|
||||
EXEC ("exec"),
|
||||
EXECCWD ("execcwd"),
|
||||
ENVRUN ("envrun"),
|
||||
KILL ("kill"),
|
||||
PS ("ps"),
|
||||
|
@ -692,7 +693,25 @@ public class DoCommand {
|
|||
theArgs[lcv - 1] = Argv[lcv];
|
||||
}
|
||||
|
||||
strReturn = StartPrg2(theArgs, cmdOut);
|
||||
strReturn = StartPrg2(theArgs, cmdOut, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
strReturn = sErrorPrefix + "Wrong number of arguments for " + Argv[0] + " command!";
|
||||
}
|
||||
break;
|
||||
|
||||
case EXECCWD:
|
||||
if (Argc >= 3)
|
||||
{
|
||||
String [] theArgs = new String [Argc - 2];
|
||||
|
||||
for (int lcv = 2; lcv < Argc; lcv++)
|
||||
{
|
||||
theArgs[lcv - 2] = Argv[lcv];
|
||||
}
|
||||
|
||||
strReturn = StartPrg2(theArgs, cmdOut, Argv[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1262,6 +1281,11 @@ private void CancelNotification()
|
|||
{
|
||||
String sRet = null;
|
||||
|
||||
File tmpFile = new java.io.File("/data/local/tests");
|
||||
if (tmpFile.exists() && tmpFile.isDirectory())
|
||||
{
|
||||
return("/data/local");
|
||||
}
|
||||
if (Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED))
|
||||
{
|
||||
sRet = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
|
@ -3463,7 +3487,7 @@ private void CancelNotification()
|
|||
return (sRet);
|
||||
}
|
||||
|
||||
public String StartPrg2(String [] progArray, OutputStream out)
|
||||
public String StartPrg2(String [] progArray, OutputStream out, String cwd)
|
||||
{
|
||||
String sRet = "";
|
||||
|
||||
|
@ -3553,7 +3577,15 @@ private void CancelNotification()
|
|||
|
||||
if (theArgs[0].contains("/") || theArgs[0].contains("\\") || !theArgs[0].contains("."))
|
||||
{
|
||||
pProc = Runtime.getRuntime().exec(theArgs, envArray);
|
||||
if (cwd != null)
|
||||
{
|
||||
File f = new File(cwd);
|
||||
pProc = Runtime.getRuntime().exec(theArgs, envArray, f);
|
||||
}
|
||||
else
|
||||
{
|
||||
pProc = Runtime.getRuntime().exec(theArgs, envArray);
|
||||
}
|
||||
|
||||
RedirOutputThread outThrd = new RedirOutputThread(pProc, out);
|
||||
outThrd.start();
|
||||
|
|
Загрузка…
Ссылка в новой задаче