Bug 563860 - update runtestsremote.py to start webserver automatically and to work on android better r=jmaher

This commit is contained in:
Clint Talbert 2010-05-27 13:02:15 -07:00
Родитель 7be883d20b
Коммит 25660290ed
2 изменённых файлов: 271 добавлений и 158 удалений

Просмотреть файл

@ -54,86 +54,25 @@ class FileError(Exception):
def __str__(self):
return self.msg
class myProc(Thread):
def __init__(self, hostip, hostport, cmd, new_line = True, sleeptime = 0):
self.cmdline = cmd
self.newline = new_line
self.sleep = sleeptime
self.host = hostip
self.port = hostport
Thread.__init__(self)
def run(self):
promptre =re.compile('.*\$\>.$')
data = ""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except:
return None
try:
s.connect((self.host, int(self.port)))
except:
s.close()
return None
try:
s.recv(1024)
except:
s.close()
return None
for cmd in self.cmdline:
if (cmd == 'quit'): break
if self.newline: cmd += '\r\n'
try:
s.send(cmd)
except:
s.close()
return None
time.sleep(int(self.sleep))
found = False
while (found == False):
try:
temp = s.recv(1024)
except:
s.close()
return None
lines = temp.split('\n')
for line in lines:
if (promptre.match(line)):
found = True
data += temp
try:
s.send('quit\r\n')
except:
s.close()
return None
try:
s.close()
except:
return None
return data
class DeviceManager:
host = ''
port = 0
debug = 3
_redo = False
deviceRoot = '/tests'
deviceRoot = None
tempRoot = os.getcwd()
base_prompt = '\$\>'
prompt_sep = '\x00'
prompt_regex = '.*' + base_prompt + prompt_sep
def __init__(self, host, port = 27020):
self.host = host
self.port = port
self._sock = None
self.getDeviceRoot()
def sendCMD(self, cmdline, newline = True, sleep = 0):
promptre = re.compile('.*\$\>.$')
promptre = re.compile(self.prompt_regex + '$')
# TODO: any commands that don't output anything and quit need to match this RE
pushre = re.compile('^push .*$')
@ -169,6 +108,7 @@ class DeviceManager:
try:
self._sock.send(cmd)
if (self.debug >= 4): print "send cmd: " + str(cmd)
except:
self._redo = True
self._sock.close()
@ -181,7 +121,7 @@ class DeviceManager:
time.sleep(int(sleep))
found = False
while (found == False):
if (self.debug >= 3): print "recv'ing..."
if (self.debug >= 4): print "recv'ing..."
try:
temp = self._sock.recv(1024)
@ -211,16 +151,16 @@ class DeviceManager:
# take a data blob and strip instances of the prompt '$>\x00'
def stripPrompt(self, data):
promptre = re.compile('.*\$\>.*')
promptre = re.compile(self.prompt_regex + '.*')
retVal = []
lines = data.split('\n')
for line in lines:
try:
while (promptre.match(line)):
pieces = line.split('\x00')
index = pieces.index("$>")
pieces = line.split(self.prompt_sep)
index = pieces.index('$>')
pieces.pop(index)
line = '\x00'.join(pieces)
line = self.prompt_sep.join(pieces)
except(ValueError):
pass
retVal.append(line)
@ -229,19 +169,16 @@ class DeviceManager:
def pushFile(self, localname, destname):
if (self.debug >= 2):
print "in push file with: " + localname + ", and: " + destname
if (self.debug >= 2): print "in push file with: " + localname + ", and: " + destname
if (self.validateFile(destname, localname) == True):
if (self.debug >= 2):
print "files are validated"
if (self.debug >= 2): print "files are validated"
return ''
if self.mkDirs(destname) == None:
print "unable to make dirs: " + destname
return None
if (self.debug >= 2):
print "sending: push " + destname
if (self.debug >= 2): print "sending: push " + destname
# sleep 5 seconds / MB
filesize = os.path.getsize(localname)
@ -252,13 +189,11 @@ class DeviceManager:
f.close()
retVal = self.sendCMD(['push ' + destname + '\r\n', data], newline = False, sleep = sleepTime)
if (retVal == None):
if (self.debug >= 2):
print "Error in sendCMD, not validating push"
if (self.debug >= 2): print "Error in sendCMD, not validating push"
return None
if (self.validateFile(destname, localname) == False):
if (self.debug >= 2):
print "file did not copy as expected"
if (self.debug >= 2): print "file did not copy as expected"
return None
return retVal
@ -286,7 +221,6 @@ class DeviceManager:
for root, dirs, files in os.walk(localDir):
parts = root.split(localDir)
for file in files:
print "examining file: " + file
remoteRoot = remoteDir + '/' + parts[1]
remoteName = remoteRoot + '/' + file
if (parts[1] == ""): remoteRoot = remoteDir
@ -346,7 +280,7 @@ class DeviceManager:
def getProcessList(self):
data = self.sendCMD(['ps', 'quit'], sleep = 3)
data = self.sendCMD(['ps'], sleep = 3)
if (data == None):
return None
@ -355,13 +289,14 @@ class DeviceManager:
files = []
for line in lines:
if (line.strip() != ''):
pidproc = line.strip().split(' ')
pidproc = line.strip().split()
if (len(pidproc) == 2):
files += [[pidproc[0], pidproc[1]]]
elif (len(pidproc) == 3):
#android returns <userID> <procID> <procName>
files += [[pidproc[1], pidproc[2], pidproc[0]]]
return files
def getMemInfo(self):
data = self.sendCMD(['mems', 'quit'])
if (data == None):
@ -375,29 +310,40 @@ class DeviceManager:
def fireProcess(self, appname):
if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
self.process = myProc(self.host, self.port, ['exec ' + appname, 'quit'])
self.process.start()
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)
def launchProcess(self, cmd, outputFile = "process.txt", cwd = ''):
if (outputFile == "process.txt"):
outputFile = self.getDeviceRoot() + '/' + "process.txt"
cmdline = subprocess.list2cmdline(cmd)
self.fireProcess(cmdline + " > " + outputFile)
handle = outputFile
return handle
#hardcoded: sleep interval of 5 seconds, timeout of 10 minutes
def communicate(self, process, timeout = 600):
interval = 5
timed_out = True
if (timeout > 0):
total_time = 0
while total_time < timeout:
time.sleep(1)
time.sleep(interval)
if (not self.poll(process)):
timed_out = False
break
total_time += 1
total_time += interval
if (timed_out == True):
return None
@ -407,7 +353,7 @@ class DeviceManager:
def poll(self, process):
try:
if (not self.process.isAlive()):
if (self.processExist(process) == None):
return None
return 1
except:
@ -420,10 +366,11 @@ class DeviceManager:
def processExist(self, appname):
pid = ''
pieces = appname.split('/')
app = pieces[-1]
pieces = appname.split(' ')
parts = pieces[0].split('/')
app = parts[-1]
procre = re.compile('.*' + app + '.*')
procList = self.getProcessList()
if (procList == None):
return None
@ -442,7 +389,6 @@ class DeviceManager:
return True
def getTempDir(self):
promptre = re.compile('.*\$\>\x00.*')
retVal = ''
data = self.sendCMD(['tmpd', 'quit'])
if (data == None):
@ -455,7 +401,7 @@ class DeviceManager:
if localFile == '':
localFile = os.path.join(self.tempRoot, "temp.txt")
promptre = re.compile('.*\$\>\x00.*')
promptre = re.compile(self.prompt_regex + '.*')
data = self.sendCMD(['cat ' + remoteFile, 'quit'], sleep = 5)
if (data == None):
return None
@ -506,8 +452,7 @@ class DeviceManager:
retVal = self.stripPrompt(data)
if (retVal != None):
retVal = retVal.strip('\n')
if (self.debug >= 3):
print "remote hash: '" + retVal + "'"
if (self.debug >= 3): print "remote hash returned: '" + retVal + "'"
return retVal
@ -530,13 +475,14 @@ class DeviceManager:
file.close()
hexval = mdsum.hexdigest()
if (self.debug >= 3):
print "local hash: '" + hexval + "'"
if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
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
# to sort those out.
# 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.
# Structure on the device is as follows:
# /tests
# /<fennec>|<firefox> --> approot
@ -546,11 +492,14 @@ class DeviceManager:
# /mochitest
def getDeviceRoot(self):
if (not self.deviceRoot):
if (self.dirExists('/tests')):
self.deviceRoot = '/tests'
else:
self.mkDir('/tests')
self.deviceRoot = '/tests'
data = self.sendCMD(['testroot'], sleep = 1)
if (data == None):
return '/tests'
self.deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
if (not self.dirExists(self.deviceRoot)):
self.mkDir(self.deviceRoot)
return self.deviceRoot
# Either we will have /tests/fennec or /tests/firefox but we will never have
@ -558,8 +507,10 @@ class DeviceManager:
def getAppRoot(self):
if (self.dirExists(self.getDeviceRoot() + '/fennec')):
return self.getDeviceRoot() + '/fennec'
else:
elif (self.dirExists(self.getDeviceRoot() + '/firefox')):
return self.getDeviceRoot() + '/firefox'
else:
return 'org.mozilla.fennec'
# Gets the directory location on the device for a specific test type
# Type is one of: xpcshell|reftest|mochitest
@ -592,8 +543,8 @@ class DeviceManager:
dir = '/'.join(parts[:-1])
elif self.fileExists('/' + filename):
dir = '/' + filename
elif self.fileExists('/tests/' + filename):
dir = '/tests/' + filename
elif self.fileExists(self.getDeviceRoot() + '/' + filename):
dir = self.getDeviceRoot() + '/' + filename
else:
return None
@ -630,3 +581,71 @@ class DeviceManager:
if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
return None
return True
#TODO: make this simpler by doing a single directive at a time
# 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)
# all - all of them
def getInfo(self, directive):
data = None
if (directive in ('os','id','uptime','systime','screen','memory','process',
'disk','power')):
data = self.sendCMD(['info ' + directive, 'quit'], sleep = 1)
else:
directive = None
data = self.sendCMD(['info', 'quit'], sleep = 1)
if (data is None):
return None
data = self.stripPrompt(data)
result = {}
if directive:
result[directive] = data.split('\n')
for i in range(len(result[directive])):
if (len(result[directive][i]) != 0):
result[directive][i] = result[directive][i].strip()
# Get rid of any empty attributes
result[directive].remove('')
else:
lines = data.split('\n')
result['id'] = lines[0]
result['os'] = lines[1]
result['systime'] = lines[2]
result['uptime'] = lines[3]
result['screen'] = lines[4]
result['memory'] = lines[5]
if (lines[6] == 'Power status'):
tmp = []
for i in range(4):
tmp.append(line[7 + i])
result['power'] = tmp
tmp = []
# Linenum is the line where the process list begins
linenum = 11
for j in range(len(lines) - linenum):
if (lines[j + linenum].strip() != ''):
procline = lines[j + linenum].split('\t')
if len(procline) == 2:
tmp.append([procline[0], procline[1]])
elif len(procline) == 3:
# Android has <userid> <procid> <procname>
# We put the userid to achieve a common format
tmp.append([procline[1], procline[2], procline[0]])
result['process'] = tmp
return result

Просмотреть файл

@ -38,12 +38,15 @@
import sys
import os
import time
import socket
import tempfile
sys.path.insert(0, os.path.abspath(os.path.realpath(os.path.dirname(sys.argv[0]))))
from automation import Automation
from runtests import Mochitest
from runtests import MochitestOptions
from runtests import MochitestServer
import devicemanager
@ -61,7 +64,7 @@ class RemoteAutomation(Automation):
def setProduct(self, productName):
self._product = productName
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime):
def waitForFinish(self, proc, utilityPath, timeout, maxTime, startTime, debuggerInfo):
status = proc.wait()
print proc.stdout
# todo: consider pulling log file from remote
@ -74,7 +77,9 @@ class RemoteAutomation(Automation):
args.remove('-foreground')
except:
pass
return app, ['--environ:NO_EM_RESTART=1'] + args
#TODO: figure out which platform require NO_EM_RESTART
# return app, ['--environ:NO_EM_RESTART=1'] + args
return app, args
def Process(self, cmd, stdout = None, stderr = None, env = None, cwd = '.'):
return self.RProcess(self._devicemanager, self._product, cmd, stdout, stderr, env, cwd)
@ -93,13 +98,13 @@ class RemoteAutomation(Automation):
# Setting timeout at 1 hour since on a remote device this takes much longer
self.timeout = 3600
time.sleep(5)
time.sleep(15)
@property
def pid(self):
hexpid = self.dm.processExist(self.procName)
if (hexpid == '' or hexpid == None):
hexpid = 0
hexpid = "0x0"
return int(hexpid, 0)
@property
@ -108,16 +113,17 @@ class RemoteAutomation(Automation):
def wait(self, timeout = None):
timer = 0
interval = 5
if timeout == None:
timeout = self.timeout
while (self.dm.process.isAlive()):
time.sleep(1)
timer += 1
while (self.dm.processExist(self.procName)):
time.sleep(interval)
timer += interval
if (timer > timeout):
break
if (timer >= timeout):
return 1
return 0
@ -140,16 +146,16 @@ class RemoteOptions(MochitestOptions):
self.add_option("--devicePort", action="store",
type = "string", dest = "devicePort",
help = "port of remote device to test")
defaults["devicePort"] = 27020
defaults["devicePort"] = 20701
self.add_option("--remoteProductName", action="store",
type = "string", dest = "remoteProductName",
help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec.exe")
defaults["remoteProductName"] = "fennec.exe"
help = "The executable's name of remote product to test - either fennec or firefox, defaults to fennec")
defaults["remoteProductName"] = "fennec"
self.add_option("--remote-logfile", action="store",
type = "string", dest = "remoteLogFile",
help = "Name of log file on the device. PLEASE ENSURE YOU HAVE CORRECT \ or / FOR THE PATH.")
help = "Name of log file on the device relative to the device root. PLEASE ONLY USE A FILENAME.")
defaults["remoteLogFile"] = None
self.add_option("--remote-webserver", action = "store",
@ -167,53 +173,66 @@ class RemoteOptions(MochitestOptions):
help = "ip address where the remote web server is hosted at")
defaults["sslPort"] = automation.DEFAULT_SSL_PORT
defaults["remoteTestRoot"] = None
defaults["logFile"] = "mochitest.log"
if (automation._product == "fennec"):
defaults["xrePath"] = "/tests/" + automation._product + "/xulrunner"
else:
defaults["xrePath"] = "/tests/" + automation._product
defaults["utilityPath"] = "/tests/bin"
defaults["certPath"] = "/tests/certs"
defaults["autorun"] = True
defaults["closeWhenDone"] = True
defaults["testPath"] = ""
defaults["app"] = "/tests/" + automation._product + "/" + defaults["remoteProductName"]
defaults["app"] = None
self.set_defaults(**defaults)
def verifyRemoteOptions(self, options, automation):
options.remoteTestRoot = automation._devicemanager.getDeviceRoot()
options.utilityPath = options.remoteTestRoot + "/bin"
options.certPath = options.remoteTestRoot + "/certs"
if options.remoteWebServer == None and os.name != "nt":
options.remoteWebServer = get_lan_ip()
elif os.name == "nt":
print "ERROR: you must specify a remoteWebServer ip address\n"
return None
options.webServer = options.remoteWebServer
if (options.deviceIP == None):
print "ERROR: you must provide a device IP"
return None
if (options.remoteLogFile == None):
options.remoteLogFile = automation._devicemanager.getDeviceRoot() + '/test.log'
# Set up our options that we depend on based on the above
productRoot = options.remoteTestRoot + "/" + automation._product
options.utilityPath = productRoot + "/bin"
# If provided, use cli value, otherwise reset as remoteTestRoot
if (options.app == None):
options.app = productRoot + "/" + options.remoteProductName
# Only reset the xrePath if it wasn't provided
if (options.xrePath == None):
if (automation._product == "fennec"):
options.xrePath = productRoot + "/xulrunner"
else:
options.xrePath = options.utilityPath
return options
def verifyOptions(self, options, mochitest):
# since we are reusing verifyOptions, it will exit if App is not found
temp = options.app
options.app = sys.argv[0]
tempPort = options.httpPort
tempSSL = options.sslPort
tempIP = options.webServer
options = MochitestOptions.verifyOptions(self, options, mochitest)
options.webServer = tempIP
options.app = temp
options.sslPort = tempSSL
options.httpPort = tempPort
if (options.remoteWebServer == None):
print "ERROR: you must provide a remote webserver ip address"
return None
else:
options.webServer = options.remoteWebServer
if (options.deviceIP == None):
print "ERROR: you must provide a device IP"
return None
if (options.remoteLogFile == None):
print "ERROR: you must specify a remote log file and ensure you have the correct \ or / slashes"
return None
# Set up our options that we depend on based on the above
options.utilityPath = "/tests/" + mochitest._automation._product + "/bin"
options.app = "/tests/" + mochitest._automation._product + "/" + options.remoteProductName
if (mochitest._automation._product == "fennec"):
options.xrePath = "/tests/" + mochitest._automation._product + "/xulrunner"
else:
options.xrePath = options.utilityPath
return options
class MochiRemote(Mochitest):
@ -226,7 +245,7 @@ class MochiRemote(Mochitest):
Mochitest.__init__(self, self._automation)
self._dm = devmgr
self.runSSLTunnel = False
self.remoteProfile = "/tests/profile"
self.remoteProfile = options.remoteTestRoot + "/profile"
self.remoteLog = options.remoteLogFile
def cleanup(self, manifest, options):
@ -234,11 +253,52 @@ class MochiRemote(Mochitest):
self._dm.removeFile(self.remoteLog)
self._dm.removeDir(self.remoteProfile)
def findPath(self, paths, filename = None):
for path in paths:
p = path
if filename:
p = os.path.join(p, filename)
if os.path.exists(self.getFullPath(p)):
return path
return None
def startWebServer(self, options):
pass
""" Create the webserver on the host and start it up """
remoteXrePath = options.xrePath
remoteProfilePath = options.profilePath
remoteUtilityPath = options.utilityPath
localAutomation = Automation()
paths = [options.xrePath, localAutomation.DIST_BIN, self._automation._product, os.path.join('..', self._automation._product)]
options.xrePath = self.findPath(paths)
if options.xrePath == None:
print "ERROR: unable to find xulrunner path for %s, please specify with --xre-path" % (os.name)
sys.exit(1)
paths.append("bin")
paths.append(os.path.join("..", "bin"))
xpcshell = "xpcshell"
if (os.name == "nt"):
xpcshell += ".exe"
if (options.utilityPath):
paths.insert(0, options.utilityPath)
options.utilityPath = self.findPath(paths, xpcshell)
if options.utilityPath == None:
print "ERROR: unable to find utility path for %s, please specify with --utility-path" % (os.name)
sys.exit(1)
options.profilePath = tempfile.mkdtemp()
self.server = MochitestServer(localAutomation, options)
self.server.start()
self.server.ensureReady(self.SERVER_STARTUP_TIMEOUT)
options.xrePath = remoteXrePath
options.utilityPath = remoteUtilityPath
options.profilePath = remoteProfilePath
def stopWebServer(self, options):
pass
self.server.stop()
def runExtensionRegistration(self, options, browserEnv):
pass
@ -260,7 +320,10 @@ class MochiRemote(Mochitest):
return retVal
def installChromeFile(self, filename, options):
path = '/'.join(options.app.split('/')[:-1])
parts = options.app.split('/')
if (parts[0] == options.app):
return "NO_CHROME_ON_DROID"
path = '/'.join(parts[:-1])
manifest = path + "/chrome/" + os.path.basename(filename)
if self._dm.pushFile(filename, manifest) == None:
raise devicemanager.FileError("Unable to install Chrome files on device.")
@ -269,6 +332,33 @@ class MochiRemote(Mochitest):
def getLogFilePath(self, logFile):
return logFile
#
# utilities to get the local ip address
#
if os.name != "nt":
import fcntl
import struct
def get_interface_ip(ifname):
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
return socket.inet_ntoa(fcntl.ioctl(
s.fileno(),
0x8915, # SIOCGIFADDR
struct.pack('256s', ifname[:15])
)[20:24])
def get_lan_ip():
ip = socket.gethostbyname(socket.gethostname())
if ip.startswith("127.") and os.name != "nt":
interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
for ifname in interfaces:
try:
ip = get_interface_ip(ifname)
break;
except IOError:
pass
return ip
def main():
scriptdir = os.path.abspath(os.path.realpath(os.path.dirname(__file__)))
dm = devicemanager.DeviceManager(None, None)
@ -278,6 +368,10 @@ def main():
dm = devicemanager.DeviceManager(options.deviceIP, options.devicePort)
auto.setDeviceManager(dm)
options = parser.verifyRemoteOptions(options, auto)
if (options == None):
print "ERROR: Invalid options specified, use --help for a list of valid options"
sys.exit(1)
productPieces = options.remoteProductName.split('.')
if (productPieces != None):