testing/mochitest/runtestsvmware.py

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 #
     2 # This Source Code Form is subject to the terms of the Mozilla Public
     3 # License, v. 2.0. If a copy of the MPL was not distributed with this
     4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6 import sys
     7 import os
     8 import re
     9 import types
    10 from optparse import OptionValueError
    11 from subprocess import PIPE
    12 from time import sleep
    13 from tempfile import mkstemp
    15 sys.path.insert(0, os.path.abspath(os.path.realpath(
    16   os.path.dirname(sys.argv[0]))))
    18 from automation import Automation
    19 from runtests import Mochitest, MochitestOptions
    21 class VMwareOptions(MochitestOptions):
    22   def __init__(self, automation, mochitest, **kwargs):
    23     defaults = {}
    24     self._automation = automation or Automation()
    25     MochitestOptions.__init__(self, mochitest.SCRIPT_DIRECTORY)
    27     def checkPathCallback(option, opt_str, value, parser):
    28       path = mochitest.getFullPath(value)
    29       if not os.path.exists(path):
    30         raise OptionValueError("Path %s does not exist for %s option"
    31                                % (path, opt_str))
    32       setattr(parser.values, option.dest, path)
    34     self.add_option("--with-vmware-vm",
    35                     action = "callback", type = "string", dest = "vmx",
    36                     callback = checkPathCallback,
    37                     help = "launches the given VM and runs mochitests inside")
    38     defaults["vmx"] = None
    40     self.add_option("--with-vmrun-executable",
    41                     action = "callback", type = "string", dest = "vmrun",
    42                     callback = checkPathCallback,
    43                     help = "specifies the vmrun.exe to use for VMware control")
    44     defaults["vmrun"] = None
    46     self.add_option("--shutdown-vm-when-done",
    47                     action = "store_true", dest = "shutdownVM",
    48                     help = "shuts down the VM when mochitests complete")
    49     defaults["shutdownVM"] = False
    51     self.add_option("--repeat-until-failure",
    52                     action = "store_true", dest = "repeatUntilFailure",
    53                     help = "Runs tests continuously until failure")
    54     defaults["repeatUntilFailure"] = False
    56     self.set_defaults(**defaults)
    58 class VMwareMochitest(Mochitest):
    59   _pathFixRegEx = re.compile(r'^[cC](\:[\\\/]+)')
    61   def convertHostPathsToGuestPaths(self, string):
    62     """ converts a path on the host machine to a path on the guest machine """
    63     # XXXbent Lame!
    64     return self._pathFixRegEx.sub(r'z\1', string)
    66   def prepareGuestArguments(self, parser, options):
    67     """ returns an array of command line arguments needed to replicate the
    68         current set of options in the guest """
    69     args = []
    70     for key in options.__dict__.keys():
    71       # Don't send these args to the vm test runner!
    72       if key == "vmrun" or key == "vmx" or key == "repeatUntilFailure":
    73         continue
    75       value = options.__dict__[key]
    76       valueType = type(value)
    78       # Find the option in the parser's list.
    79       option = None
    80       for index in range(len(parser.option_list)):
    81         if str(parser.option_list[index].dest) == key:
    82           option = parser.option_list[index]
    83           break
    84       if not option:
    85         continue
    87       # No need to pass args on the command line if they're just going to set
    88       # default values. The exception is list values... For some reason the
    89       # option parser modifies the defaults as well as the values when using the
    90       # "append" action.
    91       if value == parser.defaults[option.dest]:
    92         if valueType == types.StringType and \
    93            value == self.convertHostPathsToGuestPaths(value):
    94           continue
    95         if valueType != types.ListType:
    96           continue
    98       def getArgString(arg, option):
    99         if option.action == "store_true" or option.action == "store_false":
   100           return str(option)
   101         return "%s=%s" % (str(option),
   102                           self.convertHostPathsToGuestPaths(str(arg)))
   104       if valueType == types.ListType:
   105         # Expand lists into separate args.
   106         for item in value:
   107           args.append(getArgString(item, option))
   108       else:
   109         args.append(getArgString(value, option))
   111     return tuple(args)
   113   def launchVM(self, options):
   114     """ launches the VM and enables shared folders """
   115     # Launch VM first.
   116     self.automation.log.info("INFO | runtests.py | Launching the VM.")
   117     (result, stdout) = self.runVMCommand(self.vmrunargs + ("start", self.vmx))
   118     if result:
   119       return result
   121     # Make sure that shared folders are enabled.
   122     self.automation.log.info("INFO | runtests.py | Enabling shared folders in "
   123                              "the VM.")
   124     (result, stdout) = self.runVMCommand(self.vmrunargs + \
   125                                          ("enableSharedFolders", self.vmx))
   126     if result:
   127       return result
   129   def shutdownVM(self):
   130     """ shuts down the VM """
   131     self.automation.log.info("INFO | runtests.py | Shutting down the VM.")
   132     command = self.vmrunargs + ("runProgramInGuest", self.vmx,
   133               "c:\\windows\\system32\\shutdown.exe", "/s", "/t", "1")
   134     (result, stdout) = self.runVMCommand(command)
   135     return result
   137   def runVMCommand(self, command, expectedErrors=[], silent=False):
   138     """ runs a command in the VM using the vmrun.exe helper """
   139     commandString = ""
   140     for part in command:
   141       commandString += str(part) + " "
   142     if not silent:
   143       self.automation.log.info("INFO | runtests.py | Running command: %s"
   144                                % commandString)
   146     commonErrors = ["Error: Invalid user name or password for the guest OS",
   147                     "Unable to connect to host."]
   148     expectedErrors.extend(commonErrors)
   150     # VMware can't run commands until the VM has fully loaded so keep running
   151     # this command in a loop until it succeeds or we try 100 times.
   152     errorString = ""
   153     for i in range(100):
   154       process = Automation.Process(command, stdout=PIPE)
   155       result = process.wait()
   156       if result == 0:
   157         break
   159       for line in process.stdout.readlines():
   160         line = line.strip()
   161         if not line:
   162           continue
   163         errorString = line
   164         break
   166       expected = False
   167       for error in expectedErrors:
   168         if errorString.startswith(error):
   169           expected = True
   171       if not expected:
   172         self.automation.log.warning("WARNING | runtests.py | Command \"%s\" "
   173                                     "failed with result %d, : %s"
   174                                     % (commandString, result, errorString))
   175         break
   177       if not silent:
   178         self.automation.log.info("INFO | runtests.py | Running command again.")
   180     return (result, process.stdout.readlines())
   182   def monitorVMExecution(self, appname, logfilepath):
   183     """ monitors test execution in the VM. Waits for the test process to start,
   184         then watches the log file for test failures and checks the status of the
   185         process to catch crashes. Returns True if mochitests ran successfully.
   186     """
   187     success = True
   189     self.automation.log.info("INFO | runtests.py | Waiting for test process to "
   190                              "start.")
   192     listProcessesCommand = self.vmrunargs + ("listProcessesInGuest", self.vmx)
   193     expectedErrors = [ "Error: The virtual machine is not powered on" ]
   195     running  = False
   196     for i in range(100):
   197       (result, stdout) = self.runVMCommand(listProcessesCommand, expectedErrors,
   198                                            silent=True)
   199       if result:
   200         self.automation.log.warning("WARNING | runtests.py | Failed to get "
   201                                     "list of processes in VM!")
   202         return False
   203       for line in stdout:
   204         line = line.strip()
   205         if line.find(appname) != -1:
   206           running = True
   207           break
   208       if running:
   209         break
   210       sleep(1)
   212     self.automation.log.info("INFO | runtests.py | Found test process, "
   213                              "monitoring log.")
   215     completed = False
   216     nextLine = 0
   217     while running:
   218       log = open(logfilepath, "rb")
   219       lines = log.readlines()
   220       if len(lines) > nextLine:
   221         linesToPrint = lines[nextLine:]
   222         for line in linesToPrint:
   223           line = line.strip()
   224           if line.find("INFO SimpleTest FINISHED") != -1:
   225             completed = True
   226             continue
   227           if line.find("ERROR TEST-UNEXPECTED-FAIL") != -1:
   228             self.automation.log.info("INFO | runtests.py | Detected test "
   229                                      "failure: \"%s\"" % line)
   230             success = False
   231         nextLine = len(lines)
   232       log.close()
   234       (result, stdout) = self.runVMCommand(listProcessesCommand, expectedErrors,
   235                                            silent=True)
   236       if result:
   237         self.automation.log.warning("WARNING | runtests.py | Failed to get "
   238                                     "list of processes in VM!")
   239         return False
   241       stillRunning = False
   242       for line in stdout:
   243         line = line.strip()
   244         if line.find(appname) != -1:
   245           stillRunning = True
   246           break
   247       if stillRunning:
   248         sleep(5)
   249       else:
   250         if not completed:
   251           self.automation.log.info("INFO | runtests.py | Test process exited "
   252                                    "without finishing tests, maybe crashed.")
   253           success = False
   254         running = stillRunning
   256     return success
   258   def getCurentSnapshotList(self):
   259     """ gets a list of snapshots from the VM """
   260     (result, stdout) = self.runVMCommand(self.vmrunargs + ("listSnapshots",
   261                                                            self.vmx))
   262     snapshots = []
   263     if result != 0:
   264       self.automation.log.warning("WARNING | runtests.py | Failed to get list "
   265                                   "of snapshots in VM!")
   266       return snapshots
   267     for line in stdout:
   268       if line.startswith("Total snapshots:"):
   269         continue
   270       snapshots.append(line.strip())
   271     return snapshots
   273   def runTests(self, parser, options):
   274     """ runs mochitests in the VM """
   275     # Base args that must always be passed to vmrun.
   276     self.vmrunargs = (options.vmrun, "-T", "ws", "-gu", "Replay", "-gp",
   277                       "mozilla")
   278     self.vmrun = options.vmrun
   279     self.vmx = options.vmx
   281     result = self.launchVM(options)
   282     if result:
   283       return result
   285     if options.vmwareRecording:
   286       snapshots = self.getCurentSnapshotList()
   288     def innerRun():
   289       """ subset of the function that must run every time if we're running until
   290           failure """
   291       # Make a new shared file for the log file.
   292       (logfile, logfilepath) = mkstemp(suffix=".log")
   293       os.close(logfile)
   294       # Get args to pass to VM process. Make sure we autorun and autoclose.
   295       options.autorun = True
   296       options.closeWhenDone = True
   297       options.logFile = logfilepath
   298       self.automation.log.info("INFO | runtests.py | Determining guest "
   299                                "arguments.")
   300       runtestsArgs = self.prepareGuestArguments(parser, options)
   301       runtestsPath = self.convertHostPathsToGuestPaths(self.SCRIPT_DIRECTORY)
   302       runtestsPath = os.path.join(runtestsPath, "runtests.py")
   303       runtestsCommand = self.vmrunargs + ("runProgramInGuest", self.vmx,
   304                         "-activeWindow", "-interactive", "-noWait",
   305                         "c:\\mozilla-build\\python25\\python.exe",
   306                         runtestsPath) + runtestsArgs
   307       expectedErrors = [ "Unable to connect to host.",
   308                          "Error: The virtual machine is not powered on" ]
   309       self.automation.log.info("INFO | runtests.py | Launching guest test "
   310                                "runner.")
   311       (result, stdout) = self.runVMCommand(runtestsCommand, expectedErrors)
   312       if result:
   313         return (result, False)
   314       self.automation.log.info("INFO | runtests.py | Waiting for guest test "
   315                                "runner to complete.")
   316       mochitestsSucceeded = self.monitorVMExecution(
   317         os.path.basename(options.app), logfilepath)
   318       if mochitestsSucceeded:
   319         self.automation.log.info("INFO | runtests.py | Guest tests passed!")
   320       else:
   321         self.automation.log.info("INFO | runtests.py | Guest tests failed.")
   322       if mochitestsSucceeded and options.vmwareRecording:
   323         newSnapshots = self.getCurentSnapshotList()
   324         if len(newSnapshots) > len(snapshots):
   325           self.automation.log.info("INFO | runtests.py | Removing last "
   326                                    "recording.")
   327           (result, stdout) = self.runVMCommand(self.vmrunargs + \
   328                                                ("deleteSnapshot", self.vmx,
   329                                                 newSnapshots[-1]))
   330       self.automation.log.info("INFO | runtests.py | Removing guest log file.")
   331       for i in range(30):
   332         try:
   333           os.remove(logfilepath)
   334           break
   335         except:
   336           sleep(1)
   337           self.automation.log.warning("WARNING | runtests.py | Couldn't remove "
   338                                       "guest log file, trying again.")
   339       return (result, mochitestsSucceeded)
   341     if options.repeatUntilFailure:
   342       succeeded = True
   343       result = 0
   344       count = 1
   345       while result == 0 and succeeded:
   346         self.automation.log.info("INFO | runtests.py | Beginning mochitest run "
   347                                  "(%d)." % count)
   348         count += 1
   349         (result, succeeded) = innerRun()
   350     else:
   351       self.automation.log.info("INFO | runtests.py | Beginning mochitest run.")
   352       (result, succeeded) = innerRun()
   354     if not succeeded and options.vmwareRecording:
   355       newSnapshots = self.getCurentSnapshotList()
   356       if len(newSnapshots) > len(snapshots):
   357         self.automation.log.info("INFO | runtests.py | Failed recording saved "
   358                                  "as '%s'." % newSnapshots[-1])
   360     if result:
   361       return result
   363     if options.shutdownVM:
   364       result = self.shutdownVM()
   365       if result:
   366         return result
   368     return 0
   370 def main():
   371   automation = Automation()
   372   mochitest = VMwareMochitest(automation)
   374   parser = VMwareOptions(automation, mochitest)
   375   options, args = parser.parse_args()
   376   options = parser.verifyOptions(options, mochitest)
   377   if (options == None):
   378     sys.exit(1)
   380   if options.vmx is None:
   381     parser.error("A virtual machine must be specified with " +
   382                  "--with-vmware-vm")
   384   if options.vmrun is None:
   385     options.vmrun = os.path.join("c:\\", "Program Files", "VMware",
   386                                  "VMware VIX", "vmrun.exe")
   387     if not os.path.exists(options.vmrun):
   388       options.vmrun = os.path.join("c:\\", "Program Files (x86)", "VMware",
   389                                    "VMware VIX", "vmrun.exe")
   390       if not os.path.exists(options.vmrun):
   391         parser.error("Could not locate vmrun.exe, use --with-vmrun-executable" +
   392                      " to identify its location")
   394   sys.exit(mochitest.runTests(parser, options))
   396 if __name__ == "__main__":
   397   main()

mercurial