2010-02-23 08:48:16 -08:00
|
|
|
# ***** 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 Joel Maher.
|
|
|
|
#
|
|
|
|
# Portions created by the Initial Developer are Copyright (C) 2009
|
|
|
|
# the Initial Developer. All Rights Reserved.
|
|
|
|
#
|
|
|
|
# Contributor(s):
|
2010-07-19 08:55:42 -07:00
|
|
|
# Joel Maher <joel.maher@gmail.com> (Original Developer)
|
|
|
|
# Clint Talbert <cmtalbert@gmail.com>
|
2010-02-23 08:48:16 -08:00
|
|
|
#
|
|
|
|
# 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 *****
|
|
|
|
|
|
|
|
import socket
|
|
|
|
import time, datetime
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import hashlib
|
|
|
|
import subprocess
|
|
|
|
from threading import Thread
|
|
|
|
import traceback
|
|
|
|
import sys
|
|
|
|
|
2010-03-15 15:45:00 -07:00
|
|
|
class FileError(Exception):
|
|
|
|
" Signifies an error which occurs while doing a file operation."
|
|
|
|
|
|
|
|
def __init__(self, msg = ''):
|
|
|
|
self.msg = msg
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return self.msg
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
class DeviceManager:
|
|
|
|
host = ''
|
|
|
|
port = 0
|
2010-09-17 17:18:06 -07:00
|
|
|
debug = 2
|
2010-02-23 08:48:16 -08:00
|
|
|
_redo = False
|
2010-05-27 13:02:15 -07:00
|
|
|
deviceRoot = None
|
2010-02-23 08:48:16 -08:00
|
|
|
tempRoot = os.getcwd()
|
2010-05-27 13:02:15 -07:00
|
|
|
base_prompt = '\$\>'
|
|
|
|
prompt_sep = '\x00'
|
|
|
|
prompt_regex = '.*' + base_prompt + prompt_sep
|
2010-07-19 08:55:42 -07:00
|
|
|
agentErrorRE = re.compile('^##AGENT-ERROR##.*')
|
2010-02-23 08:48:16 -08:00
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
def __init__(self, host, port = 20701):
|
2010-02-23 08:48:16 -08:00
|
|
|
self.host = host
|
|
|
|
self.port = port
|
|
|
|
self._sock = None
|
2010-05-27 13:02:15 -07:00
|
|
|
self.getDeviceRoot()
|
2010-02-23 08:48:16 -08:00
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
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'),
|
|
|
|
re.compile('^uninst .*$')]
|
|
|
|
|
|
|
|
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):
|
|
|
|
""" 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.*'),
|
|
|
|
re.compile('^uninst .*$')]
|
|
|
|
|
|
|
|
for c in socketClosingCmds:
|
|
|
|
if (c.match(cmd)):
|
|
|
|
return True
|
2010-02-23 08:48:16 -08:00
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
return False
|
|
|
|
|
|
|
|
def sendCMD(self, cmdline, newline = True):
|
|
|
|
promptre = re.compile(self.prompt_regex + '$')
|
2010-02-23 08:48:16 -08:00
|
|
|
data = ""
|
2010-07-19 08:55:42 -07:00
|
|
|
shouldCloseSocket = False
|
|
|
|
recvGuard = 1000
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
if (self._sock == None):
|
|
|
|
try:
|
|
|
|
if (self.debug >= 1):
|
|
|
|
print "reconnecting socket"
|
|
|
|
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
except:
|
|
|
|
self._redo = True
|
|
|
|
self._sock = None
|
|
|
|
if (self.debug >= 2):
|
|
|
|
print "unable to create socket"
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
self._sock.connect((self.host, int(self.port)))
|
|
|
|
self._sock.recv(1024)
|
|
|
|
except:
|
|
|
|
self._redo = True
|
|
|
|
self._sock.close()
|
|
|
|
self._sock = None
|
|
|
|
if (self.debug >= 2):
|
|
|
|
print "unable to connect socket"
|
|
|
|
return None
|
2010-07-19 08:55:42 -07:00
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
for cmd in cmdline:
|
|
|
|
if newline: cmd += '\r\n'
|
|
|
|
|
|
|
|
try:
|
2010-07-19 08:55:42 -07:00
|
|
|
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
|
2010-05-27 13:02:15 -07:00
|
|
|
if (self.debug >= 4): print "send cmd: " + str(cmd)
|
2010-02-23 08:48:16 -08:00
|
|
|
except:
|
|
|
|
self._redo = True
|
|
|
|
self._sock.close()
|
|
|
|
self._sock = None
|
|
|
|
return None
|
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
# Check if the command should close the socket
|
|
|
|
shouldCloseSocket = self.shouldCmdCloseSocket(cmd)
|
|
|
|
|
|
|
|
# Handle responses from commands
|
|
|
|
if (self.cmdNeedsResponse(cmd)):
|
2010-02-23 08:48:16 -08:00
|
|
|
found = False
|
2010-07-19 08:55:42 -07:00
|
|
|
loopguard = 0
|
|
|
|
# TODO: We had an old sleep here but we don't need it
|
|
|
|
|
|
|
|
while (found == False and (loopguard < recvGuard)):
|
2010-09-29 16:20:33 -07:00
|
|
|
temp = ''
|
2010-05-27 13:02:15 -07:00
|
|
|
if (self.debug >= 4): print "recv'ing..."
|
2010-07-19 08:55:42 -07:00
|
|
|
|
|
|
|
# Get our response
|
2010-02-23 08:48:16 -08:00
|
|
|
try:
|
|
|
|
temp = self._sock.recv(1024)
|
2010-07-19 08:55:42 -07:00
|
|
|
if (self.debug >= 4): print "response: " + str(temp)
|
2010-02-23 08:48:16 -08:00
|
|
|
except:
|
|
|
|
self._redo = True
|
|
|
|
self._sock.close()
|
|
|
|
self._sock = None
|
|
|
|
return None
|
2010-07-19 08:55:42 -07:00
|
|
|
|
|
|
|
# If something goes wrong in the agent it will send back a string that
|
|
|
|
# starts with '##AGENT-ERROR##'
|
|
|
|
if (self.agentErrorRE.match(temp)):
|
|
|
|
data = temp
|
|
|
|
break
|
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
lines = temp.split('\n')
|
2010-07-19 08:55:42 -07:00
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
for line in lines:
|
|
|
|
if (promptre.match(line)):
|
|
|
|
found = True
|
|
|
|
data += temp
|
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
# If we violently lose the connection to the device, this loop tends to spin,
|
|
|
|
# this guard prevents that
|
2010-09-29 16:20:33 -07:00
|
|
|
if (temp == ''):
|
|
|
|
loopguard += 1
|
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
# TODO: We had an old sleep here but we don't need it
|
|
|
|
if (shouldCloseSocket == True):
|
2010-02-23 08:48:16 -08:00
|
|
|
try:
|
|
|
|
self._sock.close()
|
|
|
|
self._sock = None
|
|
|
|
except:
|
|
|
|
self._redo = True
|
|
|
|
self._sock = None
|
|
|
|
return None
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
# take a data blob and strip instances of the prompt '$>\x00'
|
|
|
|
def stripPrompt(self, data):
|
2010-05-27 13:02:15 -07:00
|
|
|
promptre = re.compile(self.prompt_regex + '.*')
|
2010-02-23 08:48:16 -08:00
|
|
|
retVal = []
|
|
|
|
lines = data.split('\n')
|
|
|
|
for line in lines:
|
|
|
|
try:
|
|
|
|
while (promptre.match(line)):
|
2010-05-27 13:02:15 -07:00
|
|
|
pieces = line.split(self.prompt_sep)
|
|
|
|
index = pieces.index('$>')
|
2010-02-23 08:48:16 -08:00
|
|
|
pieces.pop(index)
|
2010-05-27 13:02:15 -07:00
|
|
|
line = self.prompt_sep.join(pieces)
|
2010-02-23 08:48:16 -08:00
|
|
|
except(ValueError):
|
|
|
|
pass
|
|
|
|
retVal.append(line)
|
|
|
|
|
|
|
|
return '\n'.join(retVal)
|
|
|
|
|
|
|
|
|
|
|
|
def pushFile(self, localname, destname):
|
2010-09-17 17:18:06 -07:00
|
|
|
if (self.debug >= 3): print "in push file with: " + localname + ", and: " + destname
|
2010-02-23 08:48:16 -08:00
|
|
|
if (self.validateFile(destname, localname) == True):
|
2010-09-17 17:18:06 -07:00
|
|
|
if (self.debug >= 3): print "files are validated"
|
2010-02-23 08:48:16 -08:00
|
|
|
return ''
|
|
|
|
|
2010-03-15 15:45:00 -07:00
|
|
|
if self.mkDirs(destname) == None:
|
|
|
|
print "unable to make dirs: " + destname
|
|
|
|
return None
|
|
|
|
|
2010-09-17 17:18:06 -07:00
|
|
|
if (self.debug >= 3): print "sending: push " + destname
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
filesize = os.path.getsize(localname)
|
|
|
|
f = open(localname, 'rb')
|
|
|
|
data = f.read()
|
|
|
|
f.close()
|
2010-07-19 08:55:42 -07:00
|
|
|
retVal = self.sendCMD(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
|
|
|
|
|
|
|
|
if (self.debug >= 3): print "push returned: " + str(retVal)
|
|
|
|
|
|
|
|
validated = False
|
|
|
|
if (retVal):
|
|
|
|
retline = self.stripPrompt(retVal).strip()
|
|
|
|
if (retline == None or self.agentErrorRE.match(retVal)):
|
|
|
|
# Then we failed to get back a hash from agent, try manual validation
|
|
|
|
validated = self.validateFile(destname, localname)
|
|
|
|
else:
|
|
|
|
# Then we obtained a hash from push
|
|
|
|
localHash = self.getLocalHash(localname)
|
|
|
|
if (str(localHash) == str(retline)):
|
|
|
|
validated = True
|
|
|
|
else:
|
|
|
|
# We got nothing back from sendCMD, try manual validation
|
|
|
|
validated = self.validateFile(destname, localname)
|
2010-02-23 08:48:16 -08:00
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
if (validated):
|
2010-09-17 17:18:06 -07:00
|
|
|
if (self.debug >= 3): print "Push File Validated!"
|
2010-07-19 08:55:42 -07:00
|
|
|
return True
|
|
|
|
else:
|
|
|
|
if (self.debug >= 2): print "Push File Failed to Validate!"
|
2010-02-23 08:48:16 -08:00
|
|
|
return None
|
|
|
|
|
|
|
|
def mkDir(self, name):
|
2010-03-15 15:45:00 -07:00
|
|
|
return self.sendCMD(['mkdr ' + name])
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
# make directory structure on the device
|
|
|
|
def mkDirs(self, filename):
|
|
|
|
parts = filename.split('/')
|
|
|
|
name = ""
|
|
|
|
for part in parts:
|
|
|
|
if (part == parts[-1]): break
|
|
|
|
if (part != ""):
|
|
|
|
name += '/' + part
|
|
|
|
if (self.mkDir(name) == None):
|
2010-03-15 15:45:00 -07:00
|
|
|
print "failed making directory: " + str(name)
|
2010-02-23 08:48:16 -08:00
|
|
|
return None
|
2010-03-15 15:45:00 -07:00
|
|
|
return ''
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
# push localDir from host to remoteDir on the device
|
|
|
|
def pushDir(self, localDir, remoteDir):
|
|
|
|
if (self.debug >= 2): print "pushing directory: " + localDir + " to " + remoteDir
|
|
|
|
for root, dirs, files in os.walk(localDir):
|
|
|
|
parts = root.split(localDir)
|
|
|
|
for file in files:
|
|
|
|
remoteRoot = remoteDir + '/' + parts[1]
|
|
|
|
remoteName = remoteRoot + '/' + file
|
|
|
|
if (parts[1] == ""): remoteRoot = remoteDir
|
|
|
|
if (self.pushFile(os.path.join(root, file), remoteName) == None):
|
|
|
|
self.removeFile(remoteName)
|
|
|
|
if (self.pushFile(os.path.join(root, file), remoteName) == None):
|
|
|
|
return None
|
|
|
|
return True
|
|
|
|
|
|
|
|
def dirExists(self, dirname):
|
|
|
|
match = ".*" + dirname + "$"
|
|
|
|
dirre = re.compile(match)
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['cd ' + dirname, 'cwd'])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
retVal = self.stripPrompt(data)
|
|
|
|
data = retVal.split('\n')
|
|
|
|
found = False
|
|
|
|
for d in data:
|
|
|
|
if (dirre.match(d)):
|
|
|
|
found = True
|
|
|
|
|
|
|
|
return found
|
|
|
|
|
|
|
|
# Because we always have / style paths we make this a lot easier with some
|
|
|
|
# assumptions
|
|
|
|
def fileExists(self, filepath):
|
|
|
|
s = filepath.split('/')
|
|
|
|
containingpath = '/'.join(s[:-1])
|
|
|
|
listfiles = self.listFiles(containingpath)
|
|
|
|
for f in listfiles:
|
|
|
|
if (f == s[-1]):
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
# list files on the device, requires cd to directory first
|
|
|
|
def listFiles(self, rootdir):
|
|
|
|
if (self.dirExists(rootdir) == False):
|
|
|
|
return []
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['cd ' + rootdir, 'ls'])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
retVal = self.stripPrompt(data)
|
|
|
|
return retVal.split('\n')
|
|
|
|
|
|
|
|
def removeFile(self, filename):
|
|
|
|
if (self.debug>= 2): print "removing file: " + filename
|
2010-07-19 08:55:42 -07:00
|
|
|
return self.sendCMD(['rm ' + filename])
|
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
# does a recursive delete of directory on the device: rm -Rf remoteDir
|
|
|
|
def removeDir(self, remoteDir):
|
2010-07-19 08:55:42 -07:00
|
|
|
self.sendCMD(['rmdr ' + remoteDir])
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
def getProcessList(self):
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['ps'])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
|
|
|
|
retVal = self.stripPrompt(data)
|
|
|
|
lines = retVal.split('\n')
|
|
|
|
files = []
|
|
|
|
for line in lines:
|
|
|
|
if (line.strip() != ''):
|
2010-05-27 13:02:15 -07:00
|
|
|
pidproc = line.strip().split()
|
2010-02-23 08:48:16 -08:00
|
|
|
if (len(pidproc) == 2):
|
|
|
|
files += [[pidproc[0], pidproc[1]]]
|
2010-05-27 13:02:15 -07:00
|
|
|
elif (len(pidproc) == 3):
|
|
|
|
#android returns <userID> <procID> <procName>
|
|
|
|
files += [[pidproc[1], pidproc[2], pidproc[0]]]
|
2010-02-23 08:48:16 -08:00
|
|
|
return files
|
|
|
|
|
|
|
|
def getMemInfo(self):
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['mems'])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
retVal = self.stripPrompt(data)
|
|
|
|
# TODO: this is hardcoded for now
|
|
|
|
fhandle = open("memlog.txt", 'a')
|
|
|
|
fhandle.write("\n")
|
|
|
|
fhandle.write(retVal)
|
|
|
|
fhandle.close()
|
|
|
|
|
|
|
|
def fireProcess(self, appname):
|
|
|
|
if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
|
2010-05-27 13:02:15 -07:00
|
|
|
|
|
|
|
if (self.processExist(appname) != ''):
|
|
|
|
print "WARNING: process %s appears to be running already\n" % appname
|
|
|
|
|
|
|
|
self.sendCMD(['exec ' + appname])
|
|
|
|
|
|
|
|
#NOTE: we sleep for 30 seconds to allow the application to startup
|
|
|
|
time.sleep(30)
|
|
|
|
|
|
|
|
self.process = self.processExist(appname)
|
|
|
|
if (self.debug >= 4): print "got pid: " + str(self.process) + " for process: " + str(appname)
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
def launchProcess(self, cmd, outputFile = "process.txt", cwd = ''):
|
|
|
|
cmdline = subprocess.list2cmdline(cmd)
|
2010-09-17 17:18:06 -07:00
|
|
|
if (outputFile == "process.txt" or outputFile == None):
|
|
|
|
outputFile = self.getDeviceRoot() + '/' + "process.txt"
|
|
|
|
cmdline += " > " + outputFile
|
2010-05-27 13:02:15 -07:00
|
|
|
|
2010-09-17 17:18:06 -07:00
|
|
|
self.fireProcess(cmdline)
|
|
|
|
return outputFile
|
2010-02-23 08:48:16 -08:00
|
|
|
|
2010-05-27 13:02:15 -07:00
|
|
|
#hardcoded: sleep interval of 5 seconds, timeout of 10 minutes
|
2010-02-23 08:48:16 -08:00
|
|
|
def communicate(self, process, timeout = 600):
|
2010-05-27 13:02:15 -07:00
|
|
|
interval = 5
|
2010-02-23 08:48:16 -08:00
|
|
|
timed_out = True
|
|
|
|
if (timeout > 0):
|
|
|
|
total_time = 0
|
|
|
|
while total_time < timeout:
|
2010-05-27 13:02:15 -07:00
|
|
|
time.sleep(interval)
|
2010-02-23 08:48:16 -08:00
|
|
|
if (not self.poll(process)):
|
|
|
|
timed_out = False
|
|
|
|
break
|
2010-05-27 13:02:15 -07:00
|
|
|
total_time += interval
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
if (timed_out == True):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return [self.getFile(process, "temp.txt"), None]
|
|
|
|
|
|
|
|
|
|
|
|
def poll(self, process):
|
|
|
|
try:
|
2010-05-27 13:02:15 -07:00
|
|
|
if (self.processExist(process) == None):
|
2010-02-23 08:48:16 -08:00
|
|
|
return None
|
|
|
|
return 1
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
return 1
|
|
|
|
|
|
|
|
# iterates process list and returns pid if exists, otherwise ''
|
|
|
|
def processExist(self, appname):
|
|
|
|
pid = ''
|
|
|
|
|
2010-05-27 13:02:15 -07:00
|
|
|
pieces = appname.split(' ')
|
|
|
|
parts = pieces[0].split('/')
|
|
|
|
app = parts[-1]
|
2010-02-23 08:48:16 -08:00
|
|
|
procre = re.compile('.*' + app + '.*')
|
2010-05-27 13:02:15 -07:00
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
procList = self.getProcessList()
|
|
|
|
if (procList == None):
|
|
|
|
return None
|
|
|
|
|
|
|
|
for proc in procList:
|
|
|
|
if (procre.match(proc[1])):
|
|
|
|
pid = proc[0]
|
|
|
|
break
|
|
|
|
return pid
|
|
|
|
|
|
|
|
def killProcess(self, appname):
|
|
|
|
if (self.sendCMD(['kill ' + appname]) == None):
|
|
|
|
return None
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def getTempDir(self):
|
|
|
|
retVal = ''
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['tmpd'])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
return self.stripPrompt(data).strip('\n')
|
|
|
|
|
|
|
|
# copy file from device (remoteFile) to host (localFile)
|
|
|
|
def getFile(self, remoteFile, localFile = ''):
|
|
|
|
if localFile == '':
|
|
|
|
localFile = os.path.join(self.tempRoot, "temp.txt")
|
|
|
|
|
2010-05-27 13:02:15 -07:00
|
|
|
promptre = re.compile(self.prompt_regex + '.*')
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['cat ' + remoteFile])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
retVal = self.stripPrompt(data)
|
|
|
|
fhandle = open(localFile, 'wb')
|
|
|
|
fhandle.write(retVal)
|
|
|
|
fhandle.close()
|
|
|
|
return retVal
|
2010-07-19 08:55:42 -07:00
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
# copy directory structure from device (remoteDir) to host (localDir)
|
|
|
|
def getDirectory(self, remoteDir, localDir):
|
|
|
|
if (self.debug >= 2): print "getting files in '" + remoteDir + "'"
|
|
|
|
filelist = self.listFiles(remoteDir)
|
|
|
|
if (filelist == None):
|
|
|
|
return None
|
|
|
|
if (self.debug >= 3): print filelist
|
|
|
|
if not os.path.exists(localDir):
|
|
|
|
os.makedirs(localDir)
|
|
|
|
|
|
|
|
# TODO: is this a comprehensive file regex?
|
|
|
|
isFile = re.compile('^([a-zA-Z0-9_\-\. ]+)\.([a-zA-Z0-9]+)$')
|
|
|
|
for f in filelist:
|
|
|
|
if (isFile.match(f)):
|
|
|
|
if (self.getFile(remoteDir + '/' + f, os.path.join(localDir, f)) == None):
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
if (self.getDirectory(remoteDir + '/' + f, os.path.join(localDir, f)) == None):
|
|
|
|
return None
|
|
|
|
|
|
|
|
# true/false check if the two files have the same md5 sum
|
|
|
|
def validateFile(self, remoteFile, localFile):
|
|
|
|
remoteHash = self.getRemoteHash(remoteFile)
|
|
|
|
localHash = self.getLocalHash(localFile)
|
|
|
|
|
|
|
|
if (remoteHash == localHash):
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
# return the md5 sum of a remote file
|
|
|
|
def getRemoteHash(self, filename):
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['hash ' + filename])
|
2010-02-23 08:48:16 -08:00
|
|
|
if (data == None):
|
|
|
|
return ''
|
|
|
|
retVal = self.stripPrompt(data)
|
|
|
|
if (retVal != None):
|
|
|
|
retVal = retVal.strip('\n')
|
2010-05-27 13:02:15 -07:00
|
|
|
if (self.debug >= 3): print "remote hash returned: '" + retVal + "'"
|
2010-02-23 08:48:16 -08:00
|
|
|
return retVal
|
|
|
|
|
|
|
|
# return the md5 sum of a file on the host
|
|
|
|
def getLocalHash(self, filename):
|
|
|
|
file = open(filename, 'rb')
|
|
|
|
if (file == None):
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
mdsum = hashlib.md5()
|
|
|
|
except:
|
|
|
|
return None
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
data = file.read(1024)
|
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
mdsum.update(data)
|
|
|
|
|
|
|
|
file.close()
|
|
|
|
hexval = mdsum.hexdigest()
|
2010-05-27 13:02:15 -07:00
|
|
|
if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
|
2010-02-23 08:48:16 -08:00
|
|
|
return hexval
|
|
|
|
|
|
|
|
# Gets the device root for the testing area on the device
|
|
|
|
# For all devices we will use / type slashes and depend on the device-agent
|
2010-05-27 13:02:15 -07:00
|
|
|
# to sort those out. The agent will return us the device location where we
|
|
|
|
# should store things, we will then create our /tests structure relative to
|
|
|
|
# that returned path.
|
2010-02-23 08:48:16 -08:00
|
|
|
# Structure on the device is as follows:
|
|
|
|
# /tests
|
|
|
|
# /<fennec>|<firefox> --> approot
|
|
|
|
# /profile
|
|
|
|
# /xpcshell
|
|
|
|
# /reftest
|
|
|
|
# /mochitest
|
|
|
|
def getDeviceRoot(self):
|
|
|
|
if (not self.deviceRoot):
|
2010-07-19 08:55:42 -07:00
|
|
|
data = self.sendCMD(['testroot'])
|
2010-05-27 13:02:15 -07:00
|
|
|
if (data == None):
|
|
|
|
return '/tests'
|
|
|
|
self.deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
|
|
|
|
|
|
|
|
if (not self.dirExists(self.deviceRoot)):
|
|
|
|
self.mkDir(self.deviceRoot)
|
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
return self.deviceRoot
|
|
|
|
|
|
|
|
# Either we will have /tests/fennec or /tests/firefox but we will never have
|
|
|
|
# both. Return the one that exists
|
|
|
|
def getAppRoot(self):
|
|
|
|
if (self.dirExists(self.getDeviceRoot() + '/fennec')):
|
|
|
|
return self.getDeviceRoot() + '/fennec'
|
2010-05-27 13:02:15 -07:00
|
|
|
elif (self.dirExists(self.getDeviceRoot() + '/firefox')):
|
2010-02-23 08:48:16 -08:00
|
|
|
return self.getDeviceRoot() + '/firefox'
|
2010-05-27 13:02:15 -07:00
|
|
|
else:
|
|
|
|
return 'org.mozilla.fennec'
|
2010-02-23 08:48:16 -08:00
|
|
|
|
|
|
|
# Gets the directory location on the device for a specific test type
|
|
|
|
# Type is one of: xpcshell|reftest|mochitest
|
|
|
|
def getTestRoot(self, type):
|
|
|
|
if (re.search('xpcshell', type, re.I)):
|
|
|
|
self.testRoot = self.getDeviceRoot() + '/xpcshell'
|
|
|
|
elif (re.search('?(i)reftest', type)):
|
|
|
|
self.testRoot = self.getDeviceRoot() + '/reftest'
|
|
|
|
elif (re.search('?(i)mochitest', type)):
|
|
|
|
self.testRoot = self.getDeviceRoot() + '/mochitest'
|
|
|
|
return self.testRoot
|
|
|
|
|
|
|
|
# Sends a specific process ID a signal code and action.
|
|
|
|
# For Example: SIGINT and SIGDFL to process x
|
|
|
|
def signal(self, processID, signalType, signalAction):
|
|
|
|
# currently not implemented in device agent - todo
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Get a return code from process ending -- needs support on device-agent
|
|
|
|
# this is a todo
|
|
|
|
def getReturnCode(self, processID):
|
|
|
|
# todo make this real
|
|
|
|
return 0
|
|
|
|
|
|
|
|
def unpackFile(self, filename):
|
2010-03-15 15:45:00 -07:00
|
|
|
dir = ''
|
|
|
|
parts = filename.split('/')
|
|
|
|
if (len(parts) > 1):
|
|
|
|
if self.fileExists(filename):
|
|
|
|
dir = '/'.join(parts[:-1])
|
|
|
|
elif self.fileExists('/' + filename):
|
|
|
|
dir = '/' + filename
|
2010-05-27 13:02:15 -07:00
|
|
|
elif self.fileExists(self.getDeviceRoot() + '/' + filename):
|
|
|
|
dir = self.getDeviceRoot() + '/' + filename
|
2010-03-15 15:45:00 -07:00
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
return self.sendCMD(['cd ' + dir, 'unzp ' + filename])
|
|
|
|
|
|
|
|
def reboot(self, wait = False):
|
|
|
|
self.sendCMD(['rebt'])
|
|
|
|
|
|
|
|
if wait == True:
|
|
|
|
time.sleep(30)
|
|
|
|
timeout = 270
|
|
|
|
done = False
|
|
|
|
while (not done):
|
|
|
|
if self.listFiles('/') != None:
|
|
|
|
return ''
|
|
|
|
print "sleeping another 10 seconds"
|
|
|
|
time.sleep(10)
|
|
|
|
timeout = timeout - 10
|
|
|
|
if (timeout <= 0):
|
|
|
|
return None
|
|
|
|
return ''
|
|
|
|
|
2010-02-23 08:48:16 -08:00
|
|
|
# validate localDir from host to remoteDir on the device
|
|
|
|
def validateDir(self, localDir, remoteDir):
|
|
|
|
if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir
|
|
|
|
for root, dirs, files in os.walk(localDir):
|
|
|
|
parts = root.split(localDir)
|
|
|
|
for file in files:
|
|
|
|
remoteRoot = remoteDir + '/' + parts[1]
|
|
|
|
remoteRoot = remoteRoot.replace('/', '/')
|
|
|
|
if (parts[1] == ""): remoteRoot = remoteDir
|
|
|
|
remoteName = remoteRoot + '/' + file
|
|
|
|
if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
|
|
|
|
return None
|
|
|
|
return True
|
2010-05-27 13:02:15 -07:00
|
|
|
|
|
|
|
# Returns information about the device:
|
|
|
|
# Directive indicates the information you want to get, your choices are:
|
|
|
|
# os - name of the os
|
|
|
|
# id - unique id of the device
|
|
|
|
# uptime - uptime of the device
|
|
|
|
# systime - system time of the device
|
|
|
|
# screen - screen resolution
|
|
|
|
# memory - memory stats
|
|
|
|
# process - list of running processes (same as ps)
|
|
|
|
# disk - total, free, available bytes on disk
|
|
|
|
# power - power status (charge, battery temp)
|
2010-07-19 08:55:42 -07:00
|
|
|
# all - all of them - or call it with no parameters to get all the information
|
|
|
|
def getInfo(self, directive=None):
|
2010-05-27 13:02:15 -07:00
|
|
|
data = None
|
|
|
|
result = {}
|
2010-07-19 08:55:42 -07:00
|
|
|
collapseSpaces = re.compile(' +')
|
|
|
|
|
|
|
|
directives = ['os', 'id','uptime','systime','screen','memory','process',
|
|
|
|
'disk','power']
|
|
|
|
if (directive in directives):
|
|
|
|
directives = [directive]
|
|
|
|
|
|
|
|
for d in directives:
|
|
|
|
data = self.sendCMD(['info ' + d])
|
|
|
|
if (data is None):
|
|
|
|
continue
|
|
|
|
data = self.stripPrompt(data)
|
|
|
|
data = collapseSpaces.sub(' ', data)
|
|
|
|
result[d] = data.split('\n')
|
|
|
|
|
|
|
|
# Get rid of any 0 length members of the arrays
|
|
|
|
for v in result.itervalues():
|
|
|
|
while '' in v:
|
|
|
|
v.remove('')
|
|
|
|
|
|
|
|
# Format the process output
|
|
|
|
if 'process' in result:
|
|
|
|
proclist = []
|
|
|
|
for l in result['process']:
|
|
|
|
if l:
|
|
|
|
proclist.append(l.split('\t'))
|
|
|
|
result['process'] = proclist
|
|
|
|
|
|
|
|
print "results: " + str(result)
|
|
|
|
return result
|
2010-05-27 13:02:15 -07:00
|
|
|
|
2010-07-19 08:55:42 -07:00
|
|
|
"""
|
|
|
|
Installs the application onto the device
|
|
|
|
Application bundle - path to the application bundle on the device
|
|
|
|
Destination - destination directory of where application should be
|
|
|
|
installed to (optional)
|
|
|
|
Returns True or False depending on what we get back
|
|
|
|
TODO: we need a real way to know if this works or not
|
|
|
|
"""
|
|
|
|
def installApp(self, appBundlePath, destPath=None):
|
|
|
|
cmd = 'inst ' + appBundlePath
|
|
|
|
if destPath:
|
|
|
|
cmd += ' ' + destPath
|
|
|
|
data = self.sendCMD([cmd])
|
|
|
|
if (data is None):
|
|
|
|
return False
|
2010-05-27 13:02:15 -07:00
|
|
|
else:
|
2010-07-19 08:55:42 -07:00
|
|
|
return True
|
|
|
|
|
|
|
|
"""
|
|
|
|
Uninstalls the named application from device and causes a reboot.
|
|
|
|
Takes an optional argument of installation path - the path to where the application
|
|
|
|
was installed.
|
|
|
|
Returns True, but it doesn't mean anything other than the command was sent,
|
|
|
|
the reboot happens and we don't know if this succeeds or not.
|
|
|
|
"""
|
|
|
|
def uninstallAppAndReboot(self, appName, installPath=None):
|
|
|
|
cmd = 'uninst ' + appName
|
|
|
|
if installPath:
|
|
|
|
cmd += ' ' + installPath
|
|
|
|
self.sendCMD([cmd])
|
|
|
|
return True
|
2010-05-27 13:02:15 -07:00
|
|
|
|
2010-09-17 17:18:06 -07:00
|
|
|
"""
|
|
|
|
return the current time on the device
|
|
|
|
"""
|
|
|
|
def getCurrentTime(self):
|
|
|
|
data = self.sendCMD(['clok'])
|
|
|
|
if (data == None):
|
|
|
|
return None
|
|
|
|
return self.stripPrompt(data).strip('\n')
|
|
|
|
|