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/.
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
):
24 MochitestOptions
.__init
__(self
, automation
, mochitest
.SCRIPT_DIRECTORY
)
26 def checkPathCallback(option
, opt_str
, value
, parser
):
27 path
= mochitest
.getFullPath(value
)
28 if not os
.path
.exists(path
):
29 raise OptionValueError("Path %s does not exist for %s option"
31 setattr(parser
.values
, option
.dest
, path
)
33 self
.add_option("--with-vmware-vm",
34 action
= "callback", type = "string", dest
= "vmx",
35 callback
= checkPathCallback
,
36 help = "launches the given VM and runs mochitests inside")
37 defaults
["vmx"] = None
39 self
.add_option("--with-vmrun-executable",
40 action
= "callback", type = "string", dest
= "vmrun",
41 callback
= checkPathCallback
,
42 help = "specifies the vmrun.exe to use for VMware control")
43 defaults
["vmrun"] = None
45 self
.add_option("--shutdown-vm-when-done",
46 action
= "store_true", dest
= "shutdownVM",
47 help = "shuts down the VM when mochitests complete")
48 defaults
["shutdownVM"] = False
50 self
.add_option("--repeat-until-failure",
51 action
= "store_true", dest
= "repeatUntilFailure",
52 help = "Runs tests continuously until failure")
53 defaults
["repeatUntilFailure"] = False
55 self
.set_defaults(**defaults
)
57 class VMwareMochitest(Mochitest
):
58 _pathFixRegEx
= re
.compile(r
'^[cC](\:[\\\/]+)')
60 def convertHostPathsToGuestPaths(self
, string
):
61 """ converts a path on the host machine to a path on the guest machine """
63 return self
._pathFixRegEx
.sub(r
'z\1', string
)
65 def prepareGuestArguments(self
, parser
, options
):
66 """ returns an array of command line arguments needed to replicate the
67 current set of options in the guest """
69 for key
in options
.__dict
__.keys():
70 # Don't send these args to the vm test runner!
71 if key
== "vmrun" or key
== "vmx" or key
== "repeatUntilFailure":
74 value
= options
.__dict
__[key
]
75 valueType
= type(value
)
77 # Find the option in the parser's list.
79 for index
in range(len(parser
.option_list
)):
80 if str(parser
.option_list
[index
].dest
) == key
:
81 option
= parser
.option_list
[index
]
86 # No need to pass args on the command line if they're just going to set
87 # default values. The exception is list values... For some reason the
88 # option parser modifies the defaults as well as the values when using the
90 if value
== parser
.defaults
[option
.dest
]:
91 if valueType
== types
.StringType
and \
92 value
== self
.convertHostPathsToGuestPaths(value
):
94 if valueType
!= types
.ListType
:
97 def getArgString(arg
, option
):
98 if option
.action
== "store_true" or option
.action
== "store_false":
100 return "%s=%s" % (str(option
),
101 self
.convertHostPathsToGuestPaths(str(arg
)))
103 if valueType
== types
.ListType
:
104 # Expand lists into separate args.
106 args
.append(getArgString(item
, option
))
108 args
.append(getArgString(value
, option
))
112 def launchVM(self
, options
):
113 """ launches the VM and enables shared folders """
115 self
.automation
.log
.info("INFO | runtests.py | Launching the VM.")
116 (result
, stdout
) = self
.runVMCommand(self
.vmrunargs
+ ("start", self
.vmx
))
120 # Make sure that shared folders are enabled.
121 self
.automation
.log
.info("INFO | runtests.py | Enabling shared folders in "
123 (result
, stdout
) = self
.runVMCommand(self
.vmrunargs
+ \
124 ("enableSharedFolders", self
.vmx
))
128 def shutdownVM(self
):
129 """ shuts down the VM """
130 self
.automation
.log
.info("INFO | runtests.py | Shutting down the VM.")
131 command
= self
.vmrunargs
+ ("runProgramInGuest", self
.vmx
,
132 "c:\\windows\\system32\\shutdown.exe", "/s", "/t", "1")
133 (result
, stdout
) = self
.runVMCommand(command
)
136 def runVMCommand(self
, command
, expectedErrors
=[], silent
=False):
137 """ runs a command in the VM using the vmrun.exe helper """
140 commandString
+= str(part
) + " "
142 self
.automation
.log
.info("INFO | runtests.py | Running command: %s"
145 commonErrors
= ["Error: Invalid user name or password for the guest OS",
146 "Unable to connect to host."]
147 expectedErrors
.extend(commonErrors
)
149 # VMware can't run commands until the VM has fully loaded so keep running
150 # this command in a loop until it succeeds or we try 100 times.
153 process
= Automation
.Process(command
, stdout
=PIPE
)
154 result
= process
.wait()
158 for line
in process
.stdout
.readlines():
166 for error
in expectedErrors
:
167 if errorString
.startswith(error
):
171 self
.automation
.log
.warning("WARNING | runtests.py | Command \"%s\" "
172 "failed with result %d, : %s"
173 % (commandString
, result
, errorString
))
177 self
.automation
.log
.info("INFO | runtests.py | Running command again.")
179 return (result
, process
.stdout
.readlines())
181 def monitorVMExecution(self
, appname
, logfilepath
):
182 """ monitors test execution in the VM. Waits for the test process to start,
183 then watches the log file for test failures and checks the status of the
184 process to catch crashes. Returns True if mochitests ran successfully.
188 self
.automation
.log
.info("INFO | runtests.py | Waiting for test process to "
191 listProcessesCommand
= self
.vmrunargs
+ ("listProcessesInGuest", self
.vmx
)
192 expectedErrors
= [ "Error: The virtual machine is not powered on" ]
196 (result
, stdout
) = self
.runVMCommand(listProcessesCommand
, expectedErrors
,
199 self
.automation
.log
.warning("WARNING | runtests.py | Failed to get "
200 "list of processes in VM!")
204 if line
.find(appname
) != -1:
211 self
.automation
.log
.info("INFO | runtests.py | Found test process, "
217 log
= open(logfilepath
, "rb")
218 lines
= log
.readlines()
219 if len(lines
) > nextLine
:
220 linesToPrint
= lines
[nextLine
:]
221 for line
in linesToPrint
:
223 if line
.find("INFO SimpleTest FINISHED") != -1:
226 if line
.find("ERROR TEST-UNEXPECTED-FAIL") != -1:
227 self
.automation
.log
.info("INFO | runtests.py | Detected test "
228 "failure: \"%s\"" % line
)
230 nextLine
= len(lines
)
233 (result
, stdout
) = self
.runVMCommand(listProcessesCommand
, expectedErrors
,
236 self
.automation
.log
.warning("WARNING | runtests.py | Failed to get "
237 "list of processes in VM!")
243 if line
.find(appname
) != -1:
250 self
.automation
.log
.info("INFO | runtests.py | Test process exited "
251 "without finishing tests, maybe crashed.")
253 running
= stillRunning
257 def getCurentSnapshotList(self
):
258 """ gets a list of snapshots from the VM """
259 (result
, stdout
) = self
.runVMCommand(self
.vmrunargs
+ ("listSnapshots",
263 self
.automation
.log
.warning("WARNING | runtests.py | Failed to get list "
264 "of snapshots in VM!")
267 if line
.startswith("Total snapshots:"):
269 snapshots
.append(line
.strip())
272 def runTests(self
, parser
, options
):
273 """ runs mochitests in the VM """
274 # Base args that must always be passed to vmrun.
275 self
.vmrunargs
= (options
.vmrun
, "-T", "ws", "-gu", "Replay", "-gp",
277 self
.vmrun
= options
.vmrun
278 self
.vmx
= options
.vmx
280 result
= self
.launchVM(options
)
284 if options
.vmwareRecording
:
285 snapshots
= self
.getCurentSnapshotList()
288 """ subset of the function that must run every time if we're running until
290 # Make a new shared file for the log file.
291 (logfile
, logfilepath
) = mkstemp(suffix
=".log")
293 # Get args to pass to VM process. Make sure we autorun and autoclose.
294 options
.autorun
= True
295 options
.closeWhenDone
= True
296 options
.logFile
= logfilepath
297 self
.automation
.log
.info("INFO | runtests.py | Determining guest "
299 runtestsArgs
= self
.prepareGuestArguments(parser
, options
)
300 runtestsPath
= self
.convertHostPathsToGuestPaths(self
.SCRIPT_DIRECTORY
)
301 runtestsPath
= os
.path
.join(runtestsPath
, "runtests.py")
302 runtestsCommand
= self
.vmrunargs
+ ("runProgramInGuest", self
.vmx
,
303 "-activeWindow", "-interactive", "-noWait",
304 "c:\\mozilla-build\\python25\\python.exe",
305 runtestsPath
) + runtestsArgs
306 expectedErrors
= [ "Unable to connect to host.",
307 "Error: The virtual machine is not powered on" ]
308 self
.automation
.log
.info("INFO | runtests.py | Launching guest test "
310 (result
, stdout
) = self
.runVMCommand(runtestsCommand
, expectedErrors
)
312 return (result
, False)
313 self
.automation
.log
.info("INFO | runtests.py | Waiting for guest test "
314 "runner to complete.")
315 mochitestsSucceeded
= self
.monitorVMExecution(
316 os
.path
.basename(options
.app
), logfilepath
)
317 if mochitestsSucceeded
:
318 self
.automation
.log
.info("INFO | runtests.py | Guest tests passed!")
320 self
.automation
.log
.info("INFO | runtests.py | Guest tests failed.")
321 if mochitestsSucceeded
and options
.vmwareRecording
:
322 newSnapshots
= self
.getCurentSnapshotList()
323 if len(newSnapshots
) > len(snapshots
):
324 self
.automation
.log
.info("INFO | runtests.py | Removing last "
326 (result
, stdout
) = self
.runVMCommand(self
.vmrunargs
+ \
327 ("deleteSnapshot", self
.vmx
,
329 self
.automation
.log
.info("INFO | runtests.py | Removing guest log file.")
332 os
.remove(logfilepath
)
336 self
.automation
.log
.warning("WARNING | runtests.py | Couldn't remove "
337 "guest log file, trying again.")
338 return (result
, mochitestsSucceeded
)
340 if options
.repeatUntilFailure
:
344 while result
== 0 and succeeded
:
345 self
.automation
.log
.info("INFO | runtests.py | Beginning mochitest run "
348 (result
, succeeded
) = innerRun()
350 self
.automation
.log
.info("INFO | runtests.py | Beginning mochitest run.")
351 (result
, succeeded
) = innerRun()
353 if not succeeded
and options
.vmwareRecording
:
354 newSnapshots
= self
.getCurentSnapshotList()
355 if len(newSnapshots
) > len(snapshots
):
356 self
.automation
.log
.info("INFO | runtests.py | Failed recording saved "
357 "as '%s'." % newSnapshots
[-1])
362 if options
.shutdownVM
:
363 result
= self
.shutdownVM()
370 automation
= Automation()
371 mochitest
= VMwareMochitest(automation
)
373 parser
= VMwareOptions(automation
, mochitest
)
374 options
, args
= parser
.parse_args()
375 options
= parser
.verifyOptions(options
, mochitest
)
376 if (options
== None):
379 if options
.vmx
is None:
380 parser
.error("A virtual machine must be specified with " +
383 if options
.vmrun
is None:
384 options
.vmrun
= os
.path
.join("c:\\", "Program Files", "VMware",
385 "VMware VIX", "vmrun.exe")
386 if not os
.path
.exists(options
.vmrun
):
387 options
.vmrun
= os
.path
.join("c:\\", "Program Files (x86)", "VMware",
388 "VMware VIX", "vmrun.exe")
389 if not os
.path
.exists(options
.vmrun
):
390 parser
.error("Could not locate vmrun.exe, use --with-vmrun-executable" +
391 " to identify its location")
393 sys
.exit(mochitest
.runTests(parser
, options
))
395 if __name__
== "__main__":