1
# ***** BEGIN LICENSE BLOCK *****
2
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
4
# The contents of this file are subject to the Mozilla Public License Version
5
# 1.1 (the "License"); you may not use this file except in compliance with
6
# the License. You may obtain a copy of the License at
7
# http://www.mozilla.org/MPL/
9
# Software distributed under the License is distributed on an "AS IS" basis,
10
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
# for the specific language governing rights and limitations under the
14
# The Original Code is Test Automation Framework.
16
# The Initial Developer of the Original Code is Joel Maher.
18
# Portions created by the Initial Developer are Copyright (C) 2009
19
# the Initial Developer. All Rights Reserved.
22
# Joel Maher <joel.maher@gmail.com> (Original Developer)
23
# Clint Talbert <cmtalbert@gmail.com>
24
# Mark Cote <mcote@mozilla.com>
26
# Alternatively, the contents of this file may be used under the terms of
27
# either the GNU General Public License Version 2 or later (the "GPL"), or
28
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29
# in which case the provisions of the GPL or the LGPL are applicable instead
30
# of those above. If you wish to allow use of your version of this file only
31
# under the terms of either the GPL or the LGPL, and not to allow others to
32
# use your version of this file under the terms of the MPL, indicate your
33
# decision by deleting the provisions above and replace them with the notice
34
# and other provisions required by the GPL or the LGPL. If you do not delete
35
# the provisions above, a recipient may use your version of this file under
36
# the terms of any one of the MPL, the GPL or the LGPL.
38
# ***** END LICENSE BLOCK *****
47
from threading import Thread
50
from devicemanager import DeviceManager, DMError, FileError, NetworkTools
52
class DeviceManagerSUT(DeviceManager):
57
tempRoot = os.getcwd()
59
base_prompt_re = '\$\>'
61
prompt_regex = '.*(' + base_prompt_re + prompt_sep + ')'
62
agentErrorRE = re.compile('^##AGENT-WARNING##.*')
64
# TODO: member variable to indicate error conditions.
65
# This should be set to a standard error from the errno module.
66
# So, for example, when an error occurs because of a missing file/directory,
67
# before returning, the function would do something like 'self.error = errno.ENOENT'.
68
# The error would be set where appropriate--so sendCMD() could set socket errors,
69
# pushFile() and other file-related commands could set filesystem errors, etc.
71
def __init__(self, host, port = 20701, retrylimit = 5):
74
self.retrylimit = retrylimit
79
def cmdNeedsResponse(self, cmd):
80
""" Not all commands need a response from the agent:
81
* if the cmd matches the pushRE then it is the first half of push
82
and therefore we want to wait until the second half before looking
84
* rebt obviously doesn't get a response
85
* uninstall performs a reboot to ensure starting in a clean state and
86
so also doesn't look for a response
88
noResponseCmds = [re.compile('^push .*$'),
90
re.compile('^uninst .*$'),
91
re.compile('^pull .*$')]
93
for c in noResponseCmds:
97
# If the command is not in our list, then it gets a response
100
def shouldCmdCloseSocket(self, cmd):
101
""" Some commands need to close the socket after they are sent:
108
socketClosingCmds = [re.compile('^push .*$'),
109
re.compile('^quit.*'),
110
re.compile('^rebt.*'),
111
re.compile('^uninst .*$')]
113
for c in socketClosingCmds:
119
# convenience function to enable checks for agent errors
120
def verifySendCMD(self, cmdline, newline = True):
121
return self.sendCMD(cmdline, newline, False)
125
# create a wrapper for sendCMD that loops up to self.retrylimit iterations.
126
# this allows us to move the retry logic outside of the _doCMD() to make it
127
# easier for debugging in the future.
128
# note that since cmdline is a list of commands, they will all be retried if
129
# one fails. this is necessary in particular for pushFile(), where we don't want
130
# to accidentally send extra data if a failure occurs during data transmission.
132
def sendCMD(self, cmdline, newline = True, ignoreAgentErrors = True):
135
retVal = self._doCMD(cmdline, newline)
140
if ignoreAgentErrors == False:
141
if (self.agentErrorRE.match(retVal)):
142
raise DMError("error on the agent executing '%s'" % cmdline)
145
if (self.retries >= self.retrylimit):
148
raise DMError("unable to connect to %s after %s attempts" % (self.host, self.retrylimit))
150
def _doCMD(self, cmdline, newline = True):
151
promptre = re.compile(self.prompt_regex + '$')
153
shouldCloseSocket = False
156
if (self._sock == None):
158
if (self.debug >= 1):
159
print "reconnecting socket"
160
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
163
if (self.debug >= 2):
164
print "unable to create socket"
168
self._sock.connect((self.host, int(self.port)))
169
self._sock.recv(1024)
173
if (self.debug >= 2):
174
print "unable to connect socket"
178
if newline: cmd += '\r\n'
181
numbytes = self._sock.send(cmd)
182
if (numbytes != len(cmd)):
183
print "ERROR: our cmd was " + str(len(cmd)) + " bytes and we only sent " + str(numbytes)
185
if (self.debug >= 4): print "send cmd: " + str(cmd)
191
# Check if the command should close the socket
192
shouldCloseSocket = self.shouldCmdCloseSocket(cmd)
194
# Handle responses from commands
195
if (self.cmdNeedsResponse(cmd)):
199
while (found == False and (loopguard < recvGuard)):
201
if (self.debug >= 4): print "recv'ing..."
205
temp = self._sock.recv(1024)
206
if (self.debug >= 4): print "response: " + str(temp)
212
# If something goes wrong in the agent it will send back a string that
213
# starts with '##AGENT-ERROR##'
214
if (self.agentErrorRE.match(temp)):
218
lines = temp.split('\n')
221
if (promptre.match(line)):
225
# If we violently lose the connection to the device, this loop tends to spin,
226
# this guard prevents that
230
if (shouldCloseSocket == True):
241
# take a data blob and strip instances of the prompt '$>\x00'
242
def stripPrompt(self, data):
243
promptre = re.compile(self.prompt_regex + '.*')
245
lines = data.split('\n')
248
while (promptre.match(line)):
249
pieces = line.split(self.prompt_sep)
250
index = pieces.index('$>')
252
line = self.prompt_sep.join(pieces)
257
return '\n'.join(retVal)
264
def pushFile(self, localname, destname):
265
if (os.name == "nt"):
266
destname = destname.replace('\\', '/')
268
if (self.debug >= 3): print "in push file with: " + localname + ", and: " + destname
269
if (self.validateFile(destname, localname) == True):
270
if (self.debug >= 3): print "files are validated"
273
if self.mkDirs(destname) == None:
274
print "unable to make dirs: " + destname
277
if (self.debug >= 3): print "sending: push " + destname
279
filesize = os.path.getsize(localname)
280
f = open(localname, 'rb')
285
retVal = self.verifySendCMD(['push ' + destname + ' ' + str(filesize) + '\r\n', data], newline = False)
289
if (self.debug >= 3): print "push returned: " + str(retVal)
293
retline = self.stripPrompt(retVal).strip()
294
if (retline == None):
295
# Then we failed to get back a hash from agent, try manual validation
296
validated = self.validateFile(destname, localname)
298
# Then we obtained a hash from push
299
localHash = self.getLocalHash(localname)
300
if (str(localHash) == str(retline)):
303
# We got nothing back from sendCMD, try manual validation
304
validated = self.validateFile(destname, localname)
307
if (self.debug >= 3): print "Push File Validated!"
310
if (self.debug >= 2): print "Push File Failed to Validate!"
315
# success: directory name
317
def mkDir(self, name):
318
if (self.dirExists(name)):
322
retVal = self.verifySendCMD(['mkdr ' + name])
327
# make directory structure on the device
330
# success: directory structure that we created
332
def mkDirs(self, filename):
333
parts = filename.split('/')
336
if (part == parts[-1]): break
339
if (self.mkDir(name) == None):
340
print "failed making directory: " + str(name)
344
# push localDir from host to remoteDir on the device
349
def pushDir(self, localDir, remoteDir):
350
if (self.debug >= 2): print "pushing directory: %s to %s" % (localDir, remoteDir)
351
for root, dirs, files in os.walk(localDir):
352
parts = root.split(localDir)
354
remoteRoot = remoteDir + '/' + parts[1]
355
remoteName = remoteRoot + '/' + file
356
if (parts[1] == ""): remoteRoot = remoteDir
357
if (self.pushFile(os.path.join(root, file), remoteName) == False):
359
self.removeFile(remoteName)
360
if (self.pushFile(os.path.join(root, file), remoteName) == False):
368
def dirExists(self, dirname):
369
match = ".*" + dirname + "$"
370
dirre = re.compile(match)
372
data = self.verifySendCMD(['cd ' + dirname, 'cwd'])
376
retVal = self.stripPrompt(data)
377
data = retVal.split('\n')
385
# Because we always have / style paths we make this a lot easier with some
391
def fileExists(self, filepath):
392
s = filepath.split('/')
393
containingpath = '/'.join(s[:-1])
394
listfiles = self.listFiles(containingpath)
400
# list files on the device, requires cd to directory first
403
# success: array of filenames, ['file1', 'file2', ...]
405
def listFiles(self, rootdir):
406
rootdir = rootdir.rstrip('/')
407
if (self.dirExists(rootdir) == False):
410
data = self.verifySendCMD(['cd ' + rootdir, 'ls'])
414
retVal = self.stripPrompt(data)
415
files = filter(lambda x: x, retVal.split('\n'))
416
if len(files) == 1 and files[0] == '<empty>':
417
# special case on the agent: empty directories return just the string "<empty>"
423
# success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
425
def removeFile(self, filename):
426
if (self.debug>= 2): print "removing file: " + filename
428
retVal = self.verifySendCMD(['rm ' + filename])
434
# does a recursive delete of directory on the device: rm -Rf remoteDir
437
# success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
439
def removeDir(self, remoteDir):
441
retVal = self.verifySendCMD(['rmdr ' + remoteDir])
449
# success: array of process tuples
451
def getProcessList(self):
453
data = self.verifySendCMD(['ps'])
457
retVal = self.stripPrompt(data)
458
lines = retVal.split('\n')
461
if (line.strip() != ''):
462
pidproc = line.strip().split()
463
if (len(pidproc) == 2):
464
files += [[pidproc[0], pidproc[1]]]
465
elif (len(pidproc) == 3):
466
#android returns <userID> <procID> <procName>
467
files += [[pidproc[1], pidproc[2], pidproc[0]]]
474
def fireProcess(self, appname, failIfRunning=False):
476
if (self.debug >= 1): print "WARNING: fireProcess called with no command to run"
479
if (self.debug >= 2): print "FIRE PROC: '" + appname + "'"
481
if (self.processExist(appname) != None):
482
print "WARNING: process %s appears to be running already\n" % appname
487
data = self.verifySendCMD(['exec ' + appname])
491
# wait up to 30 seconds for process to start up
493
while (timeslept <= 30):
494
process = self.processExist(appname)
495
if (process is not None):
500
if (self.debug >= 4): print "got pid: %s for process: %s" % (process, appname)
505
# success: output filename
507
def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
509
if (self.debug >= 1): print "WARNING: launchProcess called without command to run"
512
cmdline = subprocess.list2cmdline(cmd)
513
if (outputFile == "process.txt" or outputFile == None):
514
outputFile = self.getDeviceRoot();
515
if outputFile is None:
517
outputFile += "/process.txt"
518
cmdline += " > " + outputFile
520
# Prepend our env to the command
521
cmdline = '%s %s' % (self.formatEnvString(env), cmdline)
523
if self.fireProcess(cmdline, failIfRunning) is None:
527
# iterates process list and returns pid if exists, otherwise None
532
def processExist(self, appname):
535
#filter out extra spaces
536
parts = filter(lambda x: x != '', appname.split(' '))
537
appname = ' '.join(parts)
539
#filter out the quoted env string if it exists
540
#ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
541
parts = appname.split('"')
543
appname = ' '.join(parts[2:]).strip()
545
pieces = appname.split(' ')
546
parts = pieces[0].split('/')
548
procre = re.compile('.*' + app + '.*')
550
procList = self.getProcessList()
554
for proc in procList:
555
if (procre.match(proc[1])):
562
# success: output from testagent
564
def killProcess(self, appname):
566
data = self.verifySendCMD(['kill ' + appname])
574
# success: tmpdir, string
576
def getTempDir(self):
578
data = self.verifySendCMD(['tmpd'])
582
return self.stripPrompt(data).strip('\n')
586
# success: filecontents
588
def catFile(self, remoteFile):
590
data = self.verifySendCMD(['cat ' + remoteFile])
594
return self.stripPrompt(data)
598
# success: output of pullfile, string
600
def pullFile(self, remoteFile):
601
"""Returns contents of remoteFile using the "pull" command.
602
The "pull" command is different from other commands in that DeviceManager
603
has to read a certain number of bytes instead of just reading to the
604
next prompt. This is more robust than the "cat" command, which will be
605
confused if the prompt string exists within the file being catted.
606
However it means we can't use the response-handling logic in sendCMD().
610
err_str = 'error returned from pull: %s' % error_msg
613
raise FileError(err_str)
615
# FIXME: We could possibly move these socket-reading functions up to
616
# the class level if we wanted to refactor sendCMD(). For now they are
617
# only used to pull files.
619
def uread(to_recv, error_msg):
620
""" unbuffered read """
622
data = self._sock.recv(to_recv)
631
def read_until_char(c, buffer, error_msg):
632
""" read until 'c' is found; buffer rest """
633
while not '\n' in buffer:
634
data = uread(1024, error_msg)
639
return buffer.partition(c)
641
def read_exact(total_to_recv, buffer, error_msg):
642
""" read exact number of 'total_to_recv' bytes """
643
while len(buffer) < total_to_recv:
644
to_recv = min(total_to_recv - len(buffer), 1024)
645
data = uread(to_recv, error_msg)
651
prompt = self.base_prompt + self.prompt_sep
654
# expected return value:
655
# <filename>,<filesize>\n<filedata>
657
# <filename>,-1\n<error message>
659
data = self.verifySendCMD(['pull ' + remoteFile])
663
# read metadata; buffer the rest
664
metadata, sep, buffer = read_until_char('\n', buffer, 'could not find metadata')
668
print 'metadata: %s' % metadata
670
filename, sep, filesizestr = metadata.partition(',')
672
err('could not find file size in returned metadata')
675
filesize = int(filesizestr)
677
err('invalid file size in returned metadata')
682
error_str, sep, buffer = read_until_char('\n', buffer, 'could not find error message')
685
# prompt should follow
686
read_exact(len(prompt), buffer, 'could not find prompt')
687
print 'DeviceManager: error pulling file: %s' % error_str
691
total_to_recv = filesize + len(prompt)
692
buffer = read_exact(total_to_recv, buffer, 'could not get all file data')
695
if buffer[-len(prompt):] != prompt:
696
err('no prompt found after file data--DeviceManager may be out of sync with agent')
698
return buffer[:-len(prompt)]
700
# copy file from device (remoteFile) to host (localFile)
703
# success: output of pullfile, string
705
def getFile(self, remoteFile, localFile = ''):
707
localFile = os.path.join(self.tempRoot, "temp.txt")
709
retVal = self.pullFile(remoteFile)
713
fhandle = open(localFile, 'wb')
714
fhandle.write(retVal)
716
if not self.validateFile(remoteFile, localFile):
717
print 'failed to validate file when downloading %s!' % remoteFile
721
# copy directory structure from device (remoteDir) to host (localDir)
723
# checkDir exists so that we don't create local directories if the
724
# remote directory doesn't exist but also so that we don't call isDir
725
# twice when recursing.
727
# success: list of files, string
729
def getDirectory(self, remoteDir, localDir, checkDir=True):
730
if (self.debug >= 2): print "getting files in '" + remoteDir + "'"
733
is_dir = self.isDir(remoteDir)
739
filelist = self.listFiles(remoteDir)
740
if (self.debug >= 3): print filelist
741
if not os.path.exists(localDir):
742
os.makedirs(localDir)
745
if f == '.' or f == '..':
747
remotePath = remoteDir + '/' + f
748
localPath = os.path.join(localDir, f)
750
is_dir = self.isDir(remotePath)
752
print 'isdir failed on file "%s"; continuing anyway...' % remotePath
755
if (self.getDirectory(remotePath, localPath, False) == None):
756
print 'failed to get directory "%s"' % remotePath
759
# It's sometimes acceptable to have getFile() return None, such as
760
# when the agent encounters broken symlinks.
761
# FIXME: This should be improved so we know when a file transfer really
763
if self.getFile(remotePath, localPath) == None:
764
print 'failed to get file "%s"; continuing anyway...' % remotePath
771
# Throws a FileError exception when null (invalid dir/filename)
772
def isDir(self, remotePath):
774
data = self.verifySendCMD(['isdir ' + remotePath])
776
# normally there should be no error here; a nonexistent file/directory will
777
# return the string "<filename>: No such file or directory".
778
# However, I've seen AGENT-WARNING returned before.
780
retVal = self.stripPrompt(data).strip()
782
raise FileError('isdir returned null')
783
return retVal == 'TRUE'
785
# true/false check if the two files have the same md5 sum
790
def validateFile(self, remoteFile, localFile):
791
remoteHash = self.getRemoteHash(remoteFile)
792
localHash = self.getLocalHash(localFile)
794
if (remoteHash == None):
797
if (remoteHash == localHash):
802
# return the md5 sum of a remote file
805
# success: MD5 hash for given filename
807
def getRemoteHash(self, filename):
809
data = self.verifySendCMD(['hash ' + filename])
813
retVal = self.stripPrompt(data)
815
retVal = retVal.strip('\n')
816
if (self.debug >= 3): print "remote hash returned: '" + retVal + "'"
819
# Gets the device root for the testing area on the device
820
# For all devices we will use / type slashes and depend on the device-agent
821
# to sort those out. The agent will return us the device location where we
822
# should store things, we will then create our /tests structure relative to
823
# that returned path.
824
# Structure on the device is as follows:
826
# /<fennec>|<firefox> --> approot
834
# success: path for device root
836
def getDeviceRoot(self):
838
data = self.verifySendCMD(['testroot'])
842
deviceRoot = self.stripPrompt(data).strip('\n') + '/tests'
844
if (not self.dirExists(deviceRoot)):
845
if (self.mkDir(deviceRoot) == None):
852
# success: output of unzip command
854
def unpackFile(self, filename):
855
devroot = self.getDeviceRoot()
856
if (devroot == None):
860
parts = filename.split('/')
862
if self.fileExists(filename):
863
dir = '/'.join(parts[:-1])
864
elif self.fileExists('/' + filename):
866
elif self.fileExists(devroot + '/' + filename):
867
dir = devroot + '/' + filename
872
data = self.verifySendCMD(['cd ' + dir, 'unzp ' + filename])
880
# success: status from test agent
882
def reboot(self, ipAddr=None, port=30000):
885
if (self.debug > 3): print "INFO: sending rebt command"
886
callbacksvrstatus = None
888
if (ipAddr is not None):
889
#create update.info file:
891
destname = '/data/data/com.mozilla.SUTAgentAndroid/files/update.info'
892
data = "%s,%s\rrebooting\r" % (ipAddr, port)
893
self.verifySendCMD(['push ' + destname + ' ' + str(len(data)) + '\r\n', data], newline = False)
897
ip, port = self.getCallbackIpAndPort(ipAddr, port)
898
cmd += " %s %s" % (ip, port)
899
# Set up our callback server
900
callbacksvr = callbackServer(ip, port, self.debug)
903
status = self.verifySendCMD([cmd])
907
if (ipAddr is not None):
908
status = callbacksvr.disconnect()
910
if (self.debug > 3): print "INFO: rebt- got status back: " + str(status)
913
# Returns information about the device:
914
# Directive indicates the information you want to get, your choices are:
915
# os - name of the os
916
# id - unique id of the device
917
# uptime - uptime of the device
918
# systime - system time of the device
919
# screen - screen resolution
920
# memory - memory stats
921
# process - list of running processes (same as ps)
922
# disk - total, free, available bytes on disk
923
# power - power status (charge, battery temp)
924
# all - all of them - or call it with no parameters to get all the information
926
# success: dict of info strings by directive name
928
def getInfo(self, directive=None):
931
collapseSpaces = re.compile(' +')
933
directives = ['os', 'id','uptime','systime','screen','memory','process',
935
if (directive in directives):
936
directives = [directive]
939
data = self.verifySendCMD(['info ' + d])
942
data = self.stripPrompt(data)
943
data = collapseSpaces.sub(' ', data)
944
result[d] = data.split('\n')
946
# Get rid of any 0 length members of the arrays
947
for k, v in result.iteritems():
948
result[k] = filter(lambda x: x != '', result[k])
950
# Format the process output
951
if 'process' in result:
953
for l in result['process']:
955
proclist.append(l.split('\t'))
956
result['process'] = proclist
958
if (self.debug >= 3): print "results: " + str(result)
962
Installs the application onto the device
963
Application bundle - path to the application bundle on the device
964
Destination - destination directory of where application should be
965
installed to (optional)
966
Returns None for success, or output if known failure
970
# success: output from agent for inst command
972
def installApp(self, appBundlePath, destPath=None):
973
cmd = 'inst ' + appBundlePath
975
cmd += ' ' + destPath
977
data = self.verifySendCMD([cmd])
981
f = re.compile('Failure')
982
for line in data.split():
988
Uninstalls the named application from device and causes a reboot.
989
Takes an optional argument of installation path - the path to where the application
991
Returns True, but it doesn't mean anything other than the command was sent,
992
the reboot happens and we don't know if this succeeds or not.
998
def uninstallAppAndReboot(self, appName, installPath=None):
999
cmd = 'uninst ' + appName
1001
cmd += ' ' + installPath
1003
data = self.verifySendCMD([cmd])
1007
if (self.debug > 3): print "uninstallAppAndReboot: " + str(data)
1011
Updates the application on the device.
1012
Application bundle - path to the application bundle on the device
1013
Process name of application - used to end the process if the applicaiton is
1015
Destination - Destination directory to where the application should be
1016
installed (optional)
1017
ipAddr - IP address to await a callback ping to let us know that the device has updated
1018
properly - defaults to current IP.
1019
port - port to await a callback ping to let us know that the device has updated properly
1020
defaults to 30000, and counts up from there if it finds a conflict
1021
Returns True if succeeds, False if not
1025
# success: text status from command or callback server
1027
def updateApp(self, appBundlePath, processName=None, destPath=None, ipAddr=None, port=30000):
1030
if (processName == None):
1031
# Then we pass '' for processName
1032
cmd += "'' " + appBundlePath
1034
cmd += processName + ' ' + appBundlePath
1037
cmd += " " + destPath
1039
if (ipAddr is not None):
1040
ip, port = self.getCallbackIpAndPort(ipAddr, port)
1041
cmd += " %s %s" % (ip, port)
1042
# Set up our callback server
1043
callbacksvr = callbackServer(ip, port, self.debug)
1045
if (self.debug >= 3): print "INFO: updateApp using command: " + str(cmd)
1048
status = self.verifySendCMD([cmd])
1052
if ipAddr is not None:
1053
status = callbacksvr.disconnect()
1055
if (self.debug >= 3): print "INFO: updateApp: got status back: " + str(status)
1060
return the current time on the device
1064
# success: time in ms
1066
def getCurrentTime(self):
1068
data = self.verifySendCMD(['clok'])
1072
return self.stripPrompt(data).strip('\n')
1075
Connect the ipaddress and port for a callback ping. Defaults to current IP address
1076
And ports starting at 30000.
1077
NOTE: the detection for current IP address only works on Linux!
1081
# success: output of unzip command
1083
def unpackFile(self, filename):
1084
devroot = self.getDeviceRoot()
1085
if (devroot == None):
1089
parts = filename.split('/')
1090
if (len(parts) > 1):
1091
if self.fileExists(filename):
1092
dir = '/'.join(parts[:-1])
1093
elif self.fileExists('/' + filename):
1094
dir = '/' + filename
1095
elif self.fileExists(devroot + '/' + filename):
1096
dir = devroot + '/' + filename
1101
data = self.verifySendCMD(['cd ' + dir, 'unzp ' + filename])
1107
def getCallbackIpAndPort(self, aIp, aPort):
1109
nettools = NetworkTools()
1111
ip = nettools.getLanIp()
1113
port = nettools.findOpenPort(ip, aPort)
1115
port = nettools.findOpenPort(ip, 30000)
1119
Returns a properly formatted env string for the agent.
1120
Input - env, which is either None, '', or a dict
1121
Output - a quoted string of the form: '"envvar1=val1,envvar2=val2..."'
1122
If env is None or '' return '' (empty quoted string)
1124
def formatEnvString(self, env):
1125
if (env == None or env == ''):
1128
retVal = '"%s"' % ','.join(map(lambda x: '%s=%s' % (x[0], x[1]), env.iteritems()))
1129
if (retVal == '""'):
1135
adjust the screen resolution on the device, REBOOT REQUIRED
1136
NOTE: this only works on a tegra ATM
1140
supported resolutions: 640x480, 800x600, 1024x768, 1152x864, 1200x1024, 1440x900, 1680x1050, 1920x1080
1142
def adjustResolution(self, width=1680, height=1050, type='hdmi'):
1143
if self.getInfo('os')['os'][0].split()[0] != 'harmony-eng':
1144
if (self.debug >= 2): print "WARNING: unable to adjust screen resolution on non Tegra device"
1147
results = self.getInfo('screen')
1148
parts = results['screen'][0].split(':')
1149
if (self.debug >= 3): print "INFO: we have a current resolution of %s, %s" % (parts[1].split()[0], parts[2].split()[0])
1151
#verify screen type is valid, and set it to the proper value (https://bugzilla.mozilla.org/show_bug.cgi?id=632895#c4)
1153
if (type == 'hdmi'):
1155
elif (type == 'vga' or type == 'crt'):
1160
#verify we have numbers
1161
if not (isinstance(width, int) and isinstance(height, int)):
1164
if (width < 100 or width > 9999):
1167
if (height < 100 or height > 9999):
1170
if (self.debug >= 3): print "INFO: adjusting screen resolution to %s, %s and rebooting" % (width, height)
1172
self.verifySendCMD(["exec setprop persist.tegra.dpy%s.mode.width %s" % (screentype, width)])
1173
self.verifySendCMD(["exec setprop persist.tegra.dpy%s.mode.height %s" % (screentype, height)])
1181
class myServer(SocketServer.TCPServer):
1182
allow_reuse_address = True
1184
class callbackServer():
1185
def __init__(self, ip, port, debuglevel):
1186
global gCallbackData
1187
if (debuglevel >= 1): print "DEBUG: gCallbackData is: %s on port: %s" % (gCallbackData, port)
1191
self.connected = False
1192
self.debug = debuglevel
1193
if (self.debug >= 3): print "Creating server with " + str(ip) + ":" + str(port)
1194
self.server = myServer((ip, port), self.myhandler)
1195
self.server_thread = Thread(target=self.server.serve_forever)
1196
self.server_thread.setDaemon(True)
1197
self.server_thread.start()
1199
def disconnect(self, step = 60, timeout = 600):
1201
if (self.debug >= 3): print "Calling disconnect on callback server"
1205
if (self.debug >= 3): print "Got data back from agent: " + str(gCallbackData)
1208
if (self.debug >= 0): print '.',
1213
if (self.debug >= 3): print "Shutting down server now"
1214
self.server.shutdown()
1216
if (self.debug >= 1): print "Unable to shutdown callback server - check for a connection on port: " + str(self.port)
1218
#sleep 1 additional step to ensure not only we are online, but all our services are online
1220
return gCallbackData
1222
class myhandler(SocketServer.BaseRequestHandler):
1224
global gCallbackData
1225
gCallbackData = self.request.recv(1024)
1226
#print "Callback Handler got data: " + str(gCallbackData)
1227
self.request.send("OK")