Bug 563860 - update runtestsremote.py to start webserver automatically and to work on android better r=jmaher
authorClint Talbert <ctalbert@mozilla.com>
Thu, 27 May 2010 13:02:15 -0700
changeset 42876 d4e6d9d727d54ecf5f22cd57b52b224d5df31849
parent 42875 d9dc6ee9190cfb1aa48dcd4d8d7e5cbe65923a0d
child 42877 f70a48308703eaff027023fc7afdfc9dc3610897
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjmaher
bugs563860
milestone1.9.3a5pre
Bug 563860 - update runtestsremote.py to start webserver automatically and to work on android better r=jmaher
build/mobile/devicemanager.py
testing/mochitest/runtestsremote.py
--- a/build/mobile/devicemanager.py
+++ b/build/mobile/devicemanager.py
@@ -49,96 +49,35 @@ 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
 
-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 .*$')
     data = ""
     noQuit = False
 
     if (self._sock == None):
       try:
@@ -164,29 +103,30 @@ class DeviceManager:
         return None
       
     for cmd in cmdline:
       if (cmd == 'quit'): break
       if newline: cmd += '\r\n'
       
       try:
         self._sock.send(cmd)
+        if (self.debug >= 4): print "send cmd: " + str(cmd)
       except:
         self._redo = True
         self._sock.close()
         self._sock = None
         return None
       
       if (pushre.match(cmd) or cmd == 'rebt'):
         noQuit = True
       elif noQuit == False:
         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)
           except:
             self._redo = True
             self._sock.close()
             self._sock = None
             return None
@@ -206,64 +146,59 @@ class DeviceManager:
         self._sock = None
         return None
 
     return data
   
   
   # 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)
 
     return '\n'.join(retVal)
   
 
   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)
     sleepsize = 1024 * 1024
     sleepTime = (int(filesize / sleepsize) * 5) + 2
     f = open(localname, 'rb')
     data = f.read()
     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
   
   def mkDir(self, name):
     return self.sendCMD(['mkdr ' + name])
   
   
@@ -281,17 +216,16 @@ class DeviceManager:
     return ''
 
   # 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:
-        print "examining file: " + file
         remoteRoot = remoteDir + '/' + parts[1]
         remoteName = remoteRoot + '/' + file
         if (parts[1] == ""): remoteRoot = remoteDir
         if (self.pushFile(os.path.join(root, file), remoteName) == None):
           time.sleep(5)
           self.removeFile(remoteName)
           time.sleep(5)
           if (self.pushFile(os.path.join(root, file), remoteName) == None):
@@ -341,94 +275,107 @@ class DeviceManager:
   
   
   # does a recursive delete of directory on the device: rm -Rf remoteDir
   def removeDir(self, remoteDir):
     self.sendCMD(['rmdr ' + remoteDir], sleep = 5)
 
 
   def getProcessList(self):
-    data = self.sendCMD(['ps', 'quit'], sleep = 3)
+    data = self.sendCMD(['ps'], sleep = 3)
     if (data == None):
       return None
       
     retVal = self.stripPrompt(data)
     lines = retVal.split('\n')
     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):
       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 + "'"
-    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
 
     return [self.getFile(process, "temp.txt"), None]
 
 
   def poll(self, process):
     try:
-      if (not self.process.isAlive()):
+      if (self.processExist(process) == None):
         return None
       return 1
     except:
       return None
     return 1
   
 
 
   # iterates process list and returns pid if exists, otherwise ''
   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
       
     for proc in procList:
       if (procre.match(proc[1])):
         pid = proc[0]
         break
@@ -437,30 +384,29 @@ class DeviceManager:
 
   def killProcess(self, appname):
     if (self.sendCMD(['kill ' + appname]) == None):
       return None
 
     return True
 
   def getTempDir(self):
-    promptre = re.compile('.*\$\>\x00.*')
     retVal = ''
     data = self.sendCMD(['tmpd', 'quit'])
     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")
   
-    promptre = re.compile('.*\$\>\x00.*')
+    promptre = re.compile(self.prompt_regex + '.*')
     data = self.sendCMD(['cat ' + remoteFile, 'quit'], sleep = 5)
     if (data == None):
       return None
     retVal = self.stripPrompt(data)
     fhandle = open(localFile, 'wb')
     fhandle.write(retVal)
     fhandle.close()
     return retVal
@@ -501,18 +447,17 @@ class DeviceManager:
   # return the md5 sum of a remote file
   def getRemoteHash(self, filename):
       data = self.sendCMD(['hash ' + filename, 'quit'], sleep = 1)
       if (data == None):
           return ''
       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
     
 
   # return the md5 sum of a file on the host
   def getLocalHash(self, filename):
       file = open(filename, 'rb')
       if (file == None):
           return None
@@ -525,46 +470,52 @@ class DeviceManager:
       while 1:
           data = file.read(1024)
           if not data:
               break
           mdsum.update(data)
 
       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
   #       /profile
   #       /xpcshell
   #       /reftest
   #       /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
   # both.  Return the one that exists
   def getAppRoot(self):
     if (self.dirExists(self.getDeviceRoot() + '/fennec')):
       return self.getDeviceRoot() + '/fennec'
+    elif (self.dirExists(self.getDeviceRoot() + '/firefox')):
+      return self.getDeviceRoot() + '/firefox'
     else:
-      return self.getDeviceRoot() + '/firefox'
+      return 'org.mozilla.fennec'
 
   # 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'
@@ -587,18 +538,18 @@ class DeviceManager:
   def unpackFile(self, filename):
     dir = ''
     parts = filename.split('/')
     if (len(parts) > 1):
       if self.fileExists(filename):
         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
 
     return self.sendCMD(['cd ' + dir, 'unzp ' + filename])
 
 
   def reboot(self, wait = False):
     self.sendCMD(['rebt'])
@@ -625,8 +576,76 @@ class DeviceManager:
       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
+
+  #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
+
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -33,22 +33,25 @@
 # 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 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
 
 class RemoteAutomation(Automation):
     _devicemanager = None
     
     def __init__(self, deviceManager, product):
         self._devicemanager = deviceManager
@@ -56,30 +59,32 @@ class RemoteAutomation(Automation):
         Automation.__init__(self)
 
     def setDeviceManager(self, deviceManager):
         self._devicemanager = deviceManager
         
     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
         return status
         
     def buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs):
         cmd, args = Automation.buildCommandLine(self, app, debuggerInfo, profileDir, testURL, extraArgs)
         # Remove -foreground if it exists, if it doesn't this just returns
         try:
           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)
 
     # be careful here as this inner class doesn't have access to outer class members    
     class RProcess(object):
         # device manager process
         dm = None
@@ -88,41 +93,42 @@ class RemoteAutomation(Automation):
             print "going to launch process: " + str(self.dm.host)
             self.proc = dm.launchProcess(cmd)
             exepath = cmd[0]
             name = exepath.split('/')[-1]
             self.procName = name
 
             # 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
         def stdout(self):
             return self.dm.getFile(self.proc)
  
         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
  
         def kill(self):
             self.dm.killProcess(self.procName)
  
 
@@ -135,26 +141,26 @@ class RemoteOptions(MochitestOptions):
         self.add_option("--deviceIP", action="store",
                     type = "string", dest = "deviceIP",
                     help = "ip address of remote device to test")
         defaults["deviceIP"] = None
 
         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",
                     type = "string", dest = "remoteWebServer",
                     help = "ip address where the remote web server is hosted at")
         defaults["remoteWebServer"] = None
 
         self.add_option("--http-port", action = "store",
@@ -162,88 +168,142 @@ class RemoteOptions(MochitestOptions):
                     help = "ip address where the remote web server is hosted at")
         defaults["httpPort"] = automation.DEFAULT_HTTP_PORT
 
         self.add_option("--ssl-port", action = "store",
                     type = "string", dest = "sslPort",
                     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):
 
     _automation = None
     _dm = None
 
     def __init__(self, automation, devmgr, options):
         self._automation = automation
         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):
         self._dm.getFile(self.remoteLog, self.localLog)
         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
         
     def buildProfile(self, options):
         manifest = Mochitest.buildProfile(self, options)
         self.localProfile = options.profilePath
         if self._dm.pushDir(options.profilePath, self.remoteProfile) == None:
@@ -255,34 +315,68 @@ class MochiRemote(Mochitest):
     def buildURLOptions(self, options):
         self.localLog = options.logFile
         options.logFile = self.remoteLog
         retVal = Mochitest.buildURLOptions(self, options)
         options.logFile = self.localLog
         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.")
         return manifest
 
     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)
     auto = RemoteAutomation(dm, "fennec")
     parser = RemoteOptions(auto, scriptdir)
     options, args = parser.parse_args()
 
     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):
       auto.setProduct(productPieces[0])
     else:
       auto.setProduct(options.remoteProductName)
 
     mochitest = MochiRemote(auto, dm, options)