PageRenderTime 55ms CodeModel.GetById 13ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/build/mobile/devicemanager.py

http://github.com/zpao/v8monkey
Python | 583 lines | 542 code | 3 blank | 38 comment | 1 complexity | 10f865d3cf50b7cc10d4cdfe73772bab MD5 | raw file
  1# ***** BEGIN LICENSE BLOCK *****
  2# Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3#
  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/
  8#
  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
 12# License.   
 13#
 14# The Original Code is Test Automation Framework.
 15#
 16# The Initial Developer of the Original Code is Joel Maher.
 17#
 18# Portions created by the Initial Developer are Copyright (C) 2009
 19# the Initial Developer. All Rights Reserved.
 20#
 21# Contributor(s):
 22#   Joel Maher <joel.maher@gmail.com> (Original Developer)
 23#   Clint Talbert <cmtalbert@gmail.com>
 24#   Mark Cote <mcote@mozilla.com>
 25#
 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.
 37#
 38# ***** END LICENSE BLOCK *****
 39
 40import time
 41import hashlib
 42import socket
 43import os
 44import re
 45
 46class FileError(Exception):
 47  " Signifies an error which occurs while doing a file operation."
 48
 49  def __init__(self, msg = ''):
 50    self.msg = msg
 51
 52  def __str__(self):
 53    return self.msg
 54
 55class DMError(Exception):
 56  "generic devicemanager exception."
 57
 58  def __init__(self, msg= ''):
 59    self.msg = msg
 60
 61  def __str__(self):
 62    return self.msg
 63
 64
 65def abstractmethod(method):
 66  line = method.func_code.co_firstlineno
 67  filename = method.func_code.co_filename
 68  def not_implemented(*args, **kwargs):
 69    raise NotImplementedError('Abstract method %s at File "%s", line %s '
 70                              'should be implemented by a concrete class' %
 71                              (repr(method), filename,line))
 72  return not_implemented
 73  
 74class DeviceManager:
 75  
 76  @abstractmethod
 77  def pushFile(self, localname, destname):
 78    """
 79    external function
 80    returns:
 81    success: True
 82    failure: False
 83    """
 84    
 85  @abstractmethod
 86  def mkDir(self, name):
 87    """
 88    external function
 89    returns:
 90    success: directory name
 91    failure: None
 92    """
 93    
 94  @abstractmethod
 95  def mkDirs(self, filename):
 96    """
 97    make directory structure on the device
 98    external function
 99    returns:
100    success: directory structure that we created
101    failure: None
102    """
103    
104  @abstractmethod
105  def pushDir(self, localDir, remoteDir):
106    """
107    push localDir from host to remoteDir on the device
108    external function
109    returns:
110    success: remoteDir
111    failure: None
112    """
113
114  @abstractmethod
115  def dirExists(self, dirname):
116    """
117    external function
118    returns:
119    success: True
120    failure: False
121    """
122    
123  @abstractmethod
124  def fileExists(self, filepath):
125    """
126    Because we always have / style paths we make this a lot easier with some
127    assumptions
128    external function
129    returns:
130    success: True
131    failure: False
132    """
133    
134  @abstractmethod
135  def listFiles(self, rootdir):
136    """
137    list files on the device, requires cd to directory first
138    external function
139    returns:
140    success: array of filenames, ['file1', 'file2', ...]
141    failure: None
142    """
143  
144  @abstractmethod
145  def removeFile(self, filename):
146    """
147    external function
148    returns:
149    success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
150    failure: None
151    """
152    
153  @abstractmethod
154  def removeDir(self, remoteDir):
155    """
156    does a recursive delete of directory on the device: rm -Rf remoteDir
157    external function
158    returns:
159    success: output of telnet, i.e. "removing file: /mnt/sdcard/tests/test.txt"
160    failure: None
161    """
162    
163  @abstractmethod
164  def getProcessList(self):
165    """
166    external function
167    returns:
168    success: array of process tuples
169    failure: None
170    """
171    
172  @abstractmethod
173  def fireProcess(self, appname, failIfRunning=False):
174    """
175    external function
176    returns:
177    success: pid
178    failure: None
179    """
180    
181  @abstractmethod
182  def launchProcess(self, cmd, outputFile = "process.txt", cwd = '', env = '', failIfRunning=False):
183    """
184    external function
185    returns:
186    success: output filename
187    failure: None
188    """
189    
190  def communicate(self, process, timeout = 600, interval = 5):
191    """
192    loops until 'process' has exited or 'timeout' seconds is reached
193    loop sleeps for 'interval' seconds between iterations
194    external function
195    returns:
196    success: [file contents, None]
197    failure: [None, None]
198    """
199    
200    timed_out = True
201    if (timeout > 0):
202      total_time = 0
203      while total_time < timeout:
204        time.sleep(interval)
205        if self.processExist(process) == None:
206          timed_out = False
207          break
208        total_time += interval
209
210    if (timed_out == True):
211      return [None, None]
212
213    return [self.getFile(process, "temp.txt"), None]
214
215  def processExist(self, appname):
216    """
217    iterates process list and returns pid if exists, otherwise None
218    external function
219    returns:
220    success: pid
221    failure: None
222    """
223    
224    pid = None
225
226    #filter out extra spaces
227    parts = filter(lambda x: x != '', appname.split(' '))
228    appname = ' '.join(parts)
229
230    #filter out the quoted env string if it exists
231    #ex: '"name=value;name2=value2;etc=..." process args' -> 'process args'
232    parts = appname.split('"')
233    if (len(parts) > 2):
234      appname = ' '.join(parts[2:]).strip()
235  
236    pieces = appname.split(' ')
237    parts = pieces[0].split('/')
238    app = parts[-1]
239
240    procList = self.getProcessList()
241    if (procList == []):
242      return None
243      
244    for proc in procList:
245      procName = proc[1].split('/')[-1]
246      if (procName == app):
247        pid = proc[0]
248        break
249    return pid
250
251
252  @abstractmethod
253  def killProcess(self, appname):
254    """
255    external function
256    returns:
257    success: output from testagent
258    failure: None
259    """
260    
261  @abstractmethod
262  def catFile(self, remoteFile):
263    """
264    external function
265    returns:
266    success: filecontents
267    failure: None
268    """
269    
270  @abstractmethod
271  def pullFile(self, remoteFile):
272    """
273    external function
274    returns:
275    success: output of pullfile, string
276    failure: None
277    """
278    
279  @abstractmethod
280  def getFile(self, remoteFile, localFile = ''):
281    """
282    copy file from device (remoteFile) to host (localFile)
283    external function
284    returns:
285    success: output of pullfile, string
286    failure: None
287    """
288    
289  @abstractmethod
290  def getDirectory(self, remoteDir, localDir, checkDir=True):
291    """
292    copy directory structure from device (remoteDir) to host (localDir)
293    external function
294    checkDir exists so that we don't create local directories if the
295    remote directory doesn't exist but also so that we don't call isDir
296    twice when recursing.
297    returns:
298    success: list of files, string
299    failure: None
300    """
301    
302  @abstractmethod
303  def isDir(self, remotePath):
304    """
305    external function
306    returns:
307    success: True
308    failure: False
309    Throws a FileError exception when null (invalid dir/filename)
310    """
311    
312  @abstractmethod
313  def validateFile(self, remoteFile, localFile):
314    """
315    true/false check if the two files have the same md5 sum
316    external function
317    returns:
318    success: True
319    failure: False
320    """
321    
322  @abstractmethod
323  def getRemoteHash(self, filename):
324    """
325    return the md5 sum of a remote file
326    internal function
327    returns:
328    success: MD5 hash for given filename
329    failure: None
330    """
331    
332  def getLocalHash(self, filename):
333    """
334    return the md5 sum of a file on the host
335    internal function
336    returns:
337    success: MD5 hash for given filename
338    failure: None
339    """
340    
341    file = open(filename, 'rb')
342    if (file == None):
343      return None
344
345    try:
346      mdsum = hashlib.md5()
347    except:
348      return None
349
350    while 1:
351      data = file.read(1024)
352      if not data:
353        break
354      mdsum.update(data)
355
356    file.close()
357    hexval = mdsum.hexdigest()
358    if (self.debug >= 3): print "local hash returned: '" + hexval + "'"
359    return hexval
360
361  @abstractmethod
362  def getDeviceRoot(self):
363    """
364    Gets the device root for the testing area on the device
365    For all devices we will use / type slashes and depend on the device-agent
366    to sort those out.  The agent will return us the device location where we
367    should store things, we will then create our /tests structure relative to
368    that returned path.
369    Structure on the device is as follows:
370    /tests
371          /<fennec>|<firefox>  --> approot
372          /profile
373          /xpcshell
374          /reftest
375          /mochitest
376    external
377    returns:
378    success: path for device root
379    failure: None
380    """
381
382  @abstractmethod
383  def getAppRoot(self):
384    """
385    Either we will have /tests/fennec or /tests/firefox but we will never have
386    both.  Return the one that exists
387    TODO: ensure we can support org.mozilla.firefox
388    external function
389    returns:
390    success: path for app root
391    failure: None
392    """
393
394  def getTestRoot(self, type):
395    """
396    Gets the directory location on the device for a specific test type
397    Type is one of: xpcshell|reftest|mochitest
398    external function
399    returns:
400    success: path for test root
401    failure: None
402    """
403    
404    devroot = self.getDeviceRoot()
405    if (devroot == None):
406      return None
407
408    if (re.search('xpcshell', type, re.I)):
409      self.testRoot = devroot + '/xpcshell'
410    elif (re.search('?(i)reftest', type)):
411      self.testRoot = devroot + '/reftest'
412    elif (re.search('?(i)mochitest', type)):
413      self.testRoot = devroot + '/mochitest'
414    return self.testRoot
415
416  def signal(self, processID, signalType, signalAction):
417    """
418    Sends a specific process ID a signal code and action.
419    For Example: SIGINT and SIGDFL to process x
420    """
421    #currently not implemented in device agent - todo
422    
423    pass
424
425  def getReturnCode(self, processID):
426    """Get a return code from process ending -- needs support on device-agent"""
427    # TODO: make this real
428    
429    return 0
430
431  @abstractmethod
432  def unpackFile(self, filename):
433    """
434    external function
435    returns:
436    success: output of unzip command
437    failure: None
438    """
439    
440  @abstractmethod
441  def reboot(self, ipAddr=None, port=30000):
442    """
443    external function
444    returns:
445    success: status from test agent
446    failure: None
447    """
448    
449  def validateDir(self, localDir, remoteDir):
450    """
451    validate localDir from host to remoteDir on the device
452    external function
453    returns:
454    success: True
455    failure: False
456    """
457    
458    if (self.debug >= 2): print "validating directory: " + localDir + " to " + remoteDir
459    for root, dirs, files in os.walk(localDir):
460      parts = root.split(localDir)
461      for file in files:
462        remoteRoot = remoteDir + '/' + parts[1]
463        remoteRoot = remoteRoot.replace('/', '/')
464        if (parts[1] == ""): remoteRoot = remoteDir
465        remoteName = remoteRoot + '/' + file
466        if (self.validateFile(remoteName, os.path.join(root, file)) <> True):
467            return False
468    return True
469    
470  @abstractmethod
471  def getInfo(self, directive=None):
472    """
473    Returns information about the device:
474    Directive indicates the information you want to get, your choices are:
475    os - name of the os
476    id - unique id of the device
477    uptime - uptime of the device
478    systime - system time of the device
479    screen - screen resolution
480    memory - memory stats
481    process - list of running processes (same as ps)
482    disk - total, free, available bytes on disk
483    power - power status (charge, battery temp)
484    all - all of them - or call it with no parameters to get all the information
485    returns:
486    success: dict of info strings by directive name
487    failure: None
488    """
489    
490  @abstractmethod
491  def installApp(self, appBundlePath, destPath=None):
492    """
493    external function
494    returns:
495    success: output from agent for inst command
496    failure: None
497    """
498
499  @abstractmethod
500  def uninstallAppAndReboot(self, appName, installPath=None):
501    """
502    external function
503    returns:
504    success: True
505    failure: None
506    """
507    
508  @abstractmethod
509  def updateApp(self, appBundlePath, processName=None,
510                destPath=None, ipAddr=None, port=30000):
511    """
512    external function
513    returns:
514    success: text status from command or callback server
515    failure: None
516    """
517  
518  @abstractmethod
519  def getCurrentTime(self):
520    """
521    external function
522    returns:
523    success: time in ms
524    failure: None
525    """
526    
527class NetworkTools:
528  def __init__(self):
529    pass
530
531  # Utilities to get the local ip address
532  def getInterfaceIp(self, ifname):
533    if os.name != "nt":
534      import fcntl
535      import struct
536      s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
537      return socket.inet_ntoa(fcntl.ioctl(
538                              s.fileno(),
539                              0x8915,  # SIOCGIFADDR
540                              struct.pack('256s', ifname[:15])
541                              )[20:24])
542    else:
543      return None
544
545  def getLanIp(self):
546    try:
547      ip = socket.gethostbyname(socket.gethostname())
548    except socket.gaierror:
549      ip = socket.gethostbyname(socket.gethostname() + ".local") # for Mac OS X
550    if ip.startswith("127.") and os.name != "nt":
551      interfaces = ["eth0","eth1","eth2","wlan0","wlan1","wifi0","ath0","ath1","ppp0"]
552      for ifname in interfaces:
553        try:
554          ip = self.getInterfaceIp(ifname)
555          break;
556        except IOError:
557          pass
558    return ip
559
560  # Gets an open port starting with the seed by incrementing by 1 each time
561  def findOpenPort(self, ip, seed):
562    try:
563      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
564      s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
565      connected = False
566      if isinstance(seed, basestring):
567        seed = int(seed)
568      maxportnum = seed + 5000 # We will try at most 5000 ports to find an open one
569      while not connected:
570        try:
571          s.bind((ip, seed))
572          connected = True
573          s.close()
574          break
575        except:          
576          if seed > maxportnum:
577            print "Could not find open port after checking 5000 ports"
578          raise
579        seed += 1
580    except:
581      print "Socket error trying to find open port"
582        
583    return seed