maemkit.py
author Clint Talbert <ctalbert@mozilla.com>
Thu, 26 Mar 2009 12:26:40 -0700
changeset 1 527dc910d556e3649553f3fb0f3beb094bf1eb89
parent 0 a0103806b64dc7ab99e4ffe6cb6ff1389d8d91b9
child 2 ba8ae7730e36a1961756aea20991bbd49c492cce
permissions -rwxr-xr-x
1[windows,qtip,tip,qbase] a7932594cea9 2009-03-26 15:21 -0400 joel [mq]: windows

import os, sys, re, copy, shutil, distutils
import ConfigParser, optparse
import datetime, time, subprocess, commands

class MaemKit(object):
  config_options = {}
  default_options = {}
  testtype = "mochitest"
  testdriver = ""
  config_file = "maemkit.cfg"
  dirtype = "/"
  debug = 0

  testtypes = ["mochitest","chrome","reftest","crashtest","xpcshell"]

  def __init__(self):
    self.defaultOptions()
    self.getConfig()
    self.getCli()
    ostype = self.config_options[self.testtype]["ostype"]
    if (ostype == "linux"): self.dirtype = "/"
    if (ostype == "macos"): self.dirtype = "/"
    if (ostype == "windows"): self.dirtype = "\\\\"

  def defaultOptions(self):
    self.default_options["debug"] = 0
    self.default_options["ostype"] = "linux"
    self.default_options["close-when-done"] = False
    self.default_options["utility-path"] = "."
    self.default_options["xre-path"] = "."
    self.default_options["certificate-path"] = "certs"
    self.default_options["log-file"] = ""
    self.default_options["autorun"] = False
    self.default_options["console-level"] = ""
    self.default_options["file-level"] = "INFO"
    self.default_options["chrome"] = False
    self.default_options["browser-chrome"] = False
    self.default_options["a11y"] = False
    self.default_options["setenv"] = []
    self.default_options["browser-arg"] = []
    self.default_options["leak-threshold"] = []
    self.default_options["fatal-assertions"] = False
    self.default_options["test-path"] = ""

    self.default_options["split-directories"] = ""
    self.default_options["split-percentage"] = 20   

    self.default_options["appname"] = "fennec"
    self.default_options["total-clients"] = 1
    self.default_options["client-number"] = 1
    self.default_options["testroot"] = "."
    self.default_options["logdir"] = "logs"

    for type in self.testtypes:
      self.config_options[type] = {}
      self.config_options[type].update(self.default_options)

  def mkLogDir(self, logdir):
    logdir = os.path.normpath(os.path.abspath(logdir))
    if (os.path.exists(logdir)):
      self.rmdir(logdir + ".bak")
      self.move(logdir, logdir + ".bak")
    self.mkdir(logdir)


  def getConfigFile(self, input_section):
    config = ConfigParser.ConfigParser()
    config.read(self.config_file)
    temp_options = {}
    if (config.has_section(input_section)):
      for item in config.items(input_section):
        temp_options[item[0]] = item[1]

    return temp_options

  def getConfig(self):
    general = self.getConfigFile("general")
    for section in self.testtypes:
      self.config_options[section].update(general)
      self.config_options[section].update(self.getConfigFile(section))

  def getCli(self):
    parser = optparse.OptionParser()

    parser.add_option("--testtype", "-t", 
                       default="mochitest", action="store", type="string", dest="testtype",
                       help="Which test to run: mochitest, chrome, reftest, crashtest")

    parser.add_option("--client-number", 
                       default="-1", action="store", type="int", dest="clientnumber",
                       help="Which client you are in a parallel run. Range is 0:total-clients")

    parser.add_option("--total-clients", 
                       default="-1", action="store", type="int", dest="totalclients",
                       help="Number of clients in your testpass in parallel mode.  Used with client-number")

    #TODO: add other cli options;  need to figure out which ones we really care about
    (cli_options, args) = parser.parse_args()
    self.testtype = cli_options.testtype
    if (cli_options.clientnumber != -1): self.config_options[self.testtype]["client-number"] = cli_options.clientnumber
    if (cli_options.totalclients != -1): self.config_options[self.testtype]["total-clients"] = cli_options.totalclients

  def shellCommand(self, cmd):
    return commands.getoutput(cmd)

  def addCommand(self, newcmd, retry=False, timeout=60):
    testroot = os.path.normpath(self.options["testroot"])
    xrepath = os.path.normpath(self.options["xre-path"])
    topsrc = testroot
    myenv = copy.copy(os.environ)
    myenv["NATIVE_TOPSRCDIR"] = testroot
    myenv["TOPSRCDIR"] = testroot
    myenv["MOZILLA_FIVE_HOME"] = xrepath
    myenv["LD_LIBRARY_PATH"] = xrepath

    parts = []
    for cmd in newcmd.split(" "):
      if (cmd.strip() != ""):
        parts.append(cmd.strip())
    if (self.debug >= 1): print " ".join(parts)
    sub_proc = subprocess.Popen(parts, bufsize=-1, env=myenv, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
    starttime = datetime.datetime.now()

    retVal = ""
    std_out = ""
    std_err = ""

    while (datetime.datetime.now() - starttime < datetime.timedelta(minutes=timeout)):
      if (retry == True):
        #TODO: consider putting this in a thread so it isn't blocking
        std_out += sub_proc.stdout.read();
        std_err += sub_proc.stderr.read();
      if (sub_proc.poll() is not None):
        std_out += sub_proc.stdout.read();
        std_err += sub_proc.stderr.read();
        break
      time.sleep(1)                                       
    retVal = std_out + "\n" + std_err

    if (datetime.datetime.now() - starttime >= datetime.timedelta(minutes=timeout)):
      retVal = "*** TIMEOUT ***"
    return retVal

  #parse log files, tally results
  #TODO: test on all types of logs
  def stitchLogs(self):
    p = 0
    f = 0
    t = 0
    myre = re.compile('.*TEST\-([A-Z]+).*')

    masterLog = open(os.path.normpath(self.options["log-file"]), "a")

    for root, dirs, files in os.walk(os.path.normpath(self.options["logdir"])):
      for logFile in files:

        #parse each line, looking for TEST-PASS (pass), TEST-UNEXPECTED-FAIL (fail), TEST-KNOWN-FAIL (todo)
        fLog = open(os.path.normpath(os.path.join(self.options["logdir"], logFile)), "r")
        for line in fLog:
          res = None
          res = myre.match(line)
          if (res):
            if (res.group(1) == "PASS"): p = p + 1
            if (res.group(1) == "UNEXPECTED"): f = f + 1
            if (res.group(1) == "KNOWN"): t = t + 1
            masterLog.write(line)

    masterLog.write("\n\n-------------------------\n")
    masterLog.write("INFO PASSED: " + str(p) + "\n")
    masterLog.write("INFO FAILED: " + str(f) + "\n")
    masterLog.write("INFO TODO: " + str(t) + "\n")
    masterLog.close()


  def cleanup(self):
    #TODO: figure out a method for killing process on wince (custom program)
    #TODO: what if we are using firefox instead of fennec?
    terminate = "killall"
    if (self.dirtype == '\\\\'): terminate = "tskill"

    try:
      self.addCommand(terminate + " fennec")
      self.addCommand(terminate + " ssltunnel")
      self.addCommand(terminate + " xpcshell")
    except:
      if (self.debug >= 1): print "error in cleanup() command"
      pass

  def getLogFileName(self, aDir):
    # returns the name of a log file from a directory
    logPrefix = "log_"
    logExtension = ".txt"
    p = re.compile("[\/\\\\:]+")
    logMiddle = p.sub('_', aDir)
    logFileName = logPrefix + logMiddle + logExtension
    return logFileName


  #split a list of tests up to be run as parallel.
  #requires: total-clients and client-number in order to
  #calculate number of list items to return
  def splitListParallel(self, list, options):
    dirlist = []
    for dir in list: dirlist.append(dir)

    if (int(options["total-clients"]) > 1):
      client_workload = []
      dirs_per_client = abs(len(list) / int(options["total-clients"])) + 1

      iter = -1
      for dir in dirlist:
        iter += 1
        if (iter < (dirs_per_client * (int(options["client-number"]) - 1))): continue
        if (iter > (dirs_per_client * int(options["client-number"]))): break
        client_workload.append(dir)
      return client_workload

    # return self if we have just 1 client
    return dirlist

  def copytree(self, src, dst):
    dest = os.path.normpath(dst)
    source = os.path.normpath(src)

    try:
      if (os.path.isdir(source)):
        shutil.copytree(source, dest)
      else:
        fname = os.path.basename(source)
        self.mkdir(dest)
        self.copyfile(source, os.path.join(dest, fname))
    except:
      if (self.debug > 1): print "exception in copytree: " + os.path.normpath(src) + " " + os.path.normpath(dst)
      pass

  def copyfile(self, src, dst):
    dest = os.path.normpath(dst)
    file = os.path.basename(os.path.normpath(src))
    root = os.path.dirname(os.path.normpath(src))
    if (file == "*"): file = ".*"
    p = re.compile(file)

    try:
      files = [file for file in os.listdir(root) if p.match(file)]
      for file in files:
        if (os.path.isdir(os.path.join(root,file)) == False):
          shutil.copy(os.path.join(root, file), dest) 
    except:
      if (self.debug > 1): print "exception in copy: " + os.path.normpath(file) + " " + os.path.normpath(dst)
      pass

  def move(self, src, dst):
    try:
      dest = os.path.normpath(dst)
      file = os.path.basename(os.path.normpath(src))
      root = os.path.dirname(os.path.normpath(src))
      if (file == "*"): file = ".*"
      p = re.compile(file)
      files = [file for file in os.listdir(root) if p.match(file)]
      for file in files:
        shutil.move(os.path.join(root, file), dest) 
    except:
      if (self.debug > 1): print "exception in move: " + os.path.join(root, file) + " " + dest
      pass

  def mkdir(self, src):
    try:
      os.makedirs(os.path.normpath(src))
    except:
      if (self.debug > 1): print "exception in mkdir: " + os.path.normpath(src)
      pass

  def rmdir(self, src):
    try:
      shutil.rmtree(os.path.normpath(src))
    except:
      if (self.debug > 1): print "exception in rmdir: " + os.path.normpath(src)
      pass