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

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

mercurial