diff options
author | Tim Jenssen <tim.jenssen@nokia.com> | 2011-02-21 16:30:31 +0100 |
---|---|---|
committer | Tim Jenssen <tim.jenssen@nokia.com> | 2011-02-21 16:41:32 +0100 |
commit | 8457830abdca9d5769e2ec1bdbfb793a05e6c5dd (patch) | |
tree | 4c9e87efd34104ec59ae31efd0394e998a2434f7 /test-framework/vmware |
init commit
Diffstat (limited to 'test-framework/vmware')
-rw-r--r-- | test-framework/vmware/cdashreporter.py | 156 | ||||
-rw-r--r-- | test-framework/vmware/control.py | 220 | ||||
-rw-r--r-- | test-framework/vmware/ftpsource.py | 117 | ||||
-rw-r--r-- | test-framework/vmware/guest.py | 160 | ||||
-rw-r--r-- | test-framework/vmware/guestconfig.py | 11 | ||||
-rw-r--r-- | test-framework/vmware/reporter.py | 96 | ||||
-rw-r--r-- | test-framework/vmware/result.py | 148 | ||||
-rw-r--r-- | test-framework/vmware/run-test.py | 111 | ||||
-rw-r--r-- | test-framework/vmware/run.py | 106 | ||||
-rw-r--r-- | test-framework/vmware/source.py | 36 | ||||
-rw-r--r-- | test-framework/vmware/testcase.py | 70 | ||||
-rw-r--r-- | test-framework/vmware/utils.py | 59 | ||||
-rw-r--r-- | test-framework/vmware/virtualmachine.py | 177 | ||||
-rw-r--r-- | test-framework/vmware/xmlutils.py | 13 |
14 files changed, 1480 insertions, 0 deletions
diff --git a/test-framework/vmware/cdashreporter.py b/test-framework/vmware/cdashreporter.py new file mode 100644 index 000000000..5acaa783e --- /dev/null +++ b/test-framework/vmware/cdashreporter.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- +import datetime, socket, os, sys, traceback, time, tempfile, httplib, urllib +from xml.sax.saxutils import XMLGenerator +from xml.sax.xmlreader import AttributesNSImpl +import result, utils +from result import exitStatusAsString +from xmlutils import startElement, endElement, writeElement + +checkerResultAsString = { result.CheckerResult.Passed:u"passed", result.CheckerResult.Failed:u"failed", result.CheckerResult.NotRun:u"notRun" } + +def formatDate( date ): + return date.strftime( '%Y%m%d-%H%M' ) + +def writeExecutionResult( gen, name, r ): + if r == None or r.hasError(): + stat = "failed" + else: + stat = "passed" + + startElement( gen, None, "Test", { u"Status":stat } ) + writeElement( gen, None, "Name", name ) + writeElement( gen, None, "FullName", name ) + startElement( gen, None, "NamedMeasurement", { u"type":u"text/string", u"name":u"Exit Code"} ) + if r: + msg = "(Unexpected)" + ec = r.exitCode + if ec == 0: + msg = "(Success)" + elif ec == 1: + msg = "(Failed)" + elif ec == 2: + msg = "(Canceled)" + + writeElement( gen, None, "Value", "Exit status: {0}; Installer exit code: {1} {2}".format( exitStatusAsString( r.exitStatus ), str( r.exitCode ), msg ) ) + else: + writeElement( gen, None, "Value", "Could not determine installation result." ) + + endElement( gen, None, "NamedMeasurement" ) + + + if r: + startElement( gen, None, "NamedMeasurement", { u"type":u"numeric/double", u"name":u"Execution Time"} ) + writeElement( gen, None, "Value", str( r.executionTime ) ) + endElement( gen, None, "NamedMeasurement" ) + + + endElement( gen, None, "Test" ) + +def writeInternalError( gen, errorstr, num ): + startElement( gen, None, "Test", { u"Status":u"failed"} ) + name = "InternalError{0}".format( num ) + writeElement( gen, None, "Name", name ) + writeElement( gen, None, "FullName", name ) + startElement( gen, None, "NamedMeasurement", { u"type":u"text/string", u"name":u"Completion Status"} ) + writeElement( gen, None, "Value", errorstr ) + endElement( gen, None, "NamedMeasurement" ) + endElement( gen, None, "Test" ) + +def writeTest( gen, test ): + startElement( gen, None, "Test", { u"Status":checkerResultAsString[test.result]} ) + writeElement( gen, None, "Name", test.name ) + writeElement( gen, None, "FullName", test.name ) + startElement( gen, None, "NamedMeasurement", { u"type":u"text/string", u"name":u"Completion Status"} ) + writeElement( gen, None, "Value", test.errorString ) + endElement( gen, None, "NamedMeasurement" ) + endElement( gen, None, "Test" ) + +class CDashReporter: + def __init__( self, host, location, project ): + self._host = host + self._location = location + self._project = project + + def reportException( self ): + sys.stderr.write( traceback.format_exc( 15 ) + '\n' ) + #TODO + + def reportResult( self, result ): + self._buildStamp = '{0}-Nightly'.format( datetime.datetime.now().strftime( '%Y%m%d-%H%M' ), utils.randomString( 4 ) ) + try: + #generate XML + self.genXml( self.genTestXml, result ) + #generate Tests.xml + #submitFile('Tests.xml') + except: + raise + + def genTestXml( self, gen, result ): + name = "{0} on {1}".format( result._installer.sourceFilename, result._vm.name() ) + startElement( gen, None, "Site", { u"BuildStamp":self._buildStamp, u"Name":name, u"Generator":u"VMTester 0.1" } ) + startElement( gen, None, "Testing" ) + writeElement( gen, None, "StartDateTime", formatDate( result._testStart ) ) + startElement( gen, None, "TestList" ) + internal = result._internalErrors + for i in range( 0, len( internal ) - 1 ): + writeElement( gen, None, "Test", "InternalError{0}".format( i ) ) + + for stepNum in range( len( result._stepResults ) ): + step = result._stepResults[stepNum] + writeElement( gen, None, "Test", "installer-run{0}".format( stepNum ) ) + for i in step.checkerResults: + writeElement( gen, None, "Test", i.name ) + endElement( gen, None, "TestList" ) + + for i in range( 0, len( internal ) - 1 ): + writeInternalError( gen, internal[i], i ) + for stepNum in range( len( result._stepResults ) ): + step = result._stepResults[stepNum] + writeExecutionResult( gen, "installer-run{0}".format( stepNum ), step.executionResult ) + for i in step.checkerResults: + writeTest( gen, i ) + endElement( gen, None, "Testing" ) + endElement( gen, None, "Site" ) + + def genXml( self, fillFunc, result ): + f = None + try: + f = tempfile.NamedTemporaryFile( delete=False ) + gen = XMLGenerator( f, 'utf-8' ) + gen.startDocument() + fillFunc( gen, result ) + gen.endDocument() + f.close() + self.submitFile( f.name ) + finally: + if f: + os.unlink( f.name ) + + def submitFile( self, path ): + f = file( path ) + params = urllib.urlencode( {'project': self._project} ) + conn = None + try: + try: + conn = httplib.HTTPConnection( self._host ) + conn.request( "POST", "{0}/submit.php?project={1}".format( self._location, self._project ), f ) + response = conn.getresponse() + print response.status, response.reason + #TODO check result + finally: + if conn: + conn.close() + except: + #TODO: if submitting to cdash fails, try to notify the admin (mail?) + raise + +if __name__ == "__main__": + r = result.Result() + r.testStarted() + r.addCheckerResult( result.CheckerResult( "test1", result.CheckerResult.Passed, "" ) ) + r.addCheckerResult( result.CheckerResult( "test2", result.CheckerResult.Failed, "Something went wrong, dude!" ) ) + time.sleep( 1 ) + r.testFinished() + cr = CDashReporter( "http://localhost", "/CDash", "test1" ) + cr.reportResult( r ) + diff --git a/test-framework/vmware/control.py b/test-framework/vmware/control.py new file mode 100644 index 000000000..b24c33ea9 --- /dev/null +++ b/test-framework/vmware/control.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +import ConfigParser, datetime, os, string, sys, time, platform +import testcase, utils, result, virtualmachine +from virtualmachine import VMException +from xml.sax import make_parser +from xml.sax.handler import ContentHandler + +class ControlException( Exception ): + def __init__( self, value ): + self.value = value + def __str__( self ): + return repr( self.value ) + +class Handler( ContentHandler ): + def __init__( self, res ): + self._res = res + self._inResult = False + self._buf = "" + + def startElement( self, name, attrs ): + self._inResult = False + if name == "result": + self._inResult = True + self._name = attrs['name'] + self._status = attrs['status'] + + def endElement( self, name ): + if name == 'result': + trimmed = string.strip( self._buf ) + if self._status == "passed": + stat = result.CheckerResult.Passed + else: + stat = result.CheckerResult.Failed + self._res.addCheckerResult( result.CheckerResult( self._name, stat, trimmed ) ) + self._inResult = False + + self._buf = "" + + def characters( self, ch ): + if self._inResult: + self._buf += ch + +class Control: + def __init__( self, vmrun, checkerDir, source, reporter ): + self._vmrun = vmrun + self._checkerDir = checkerDir + self._vms = [] + self._testcases = [] + self._source = source + self._reporter = reporter + self._guiEnabled = True + self._createErrorSnapshots = False + self._hostType = "" + self._hostLocation = "" + self._hostUsername = "" + self._hostPassword = "" + + def setGuiEnabled( self, usegui ): + self._guiEnabled = usegui + for i in self._vms: + i.setGuiEnabled( usegui ) + + def setCreateErrorSnapshots( self, createSnapshots ): + self._createErrorSnapshots = createSnapshots + + def setRemoteHost( self, type, loc, user, pw ): + self._hostType = type + self._hostLocation = loc + self._hostUsername = user + self._hostPassword = pw + for vm in self._vms: + if not vm.isRemote(): + vm.setRemoteHost( self._hostType, self._hostLocation, self._hostUsername, self._hostPassword ) + + def addVM( self, cfgpath ): + config = ConfigParser.SafeConfigParser() + config.read( cfgpath ) + vm = virtualmachine.fromVMRunAndPath( self._vmrun, cfgpath ) + vm.setGuiEnabled( self._guiEnabled ) + if len( self._hostType ) > 0 and not vm.isRemote(): + vm.setRemoteHost( self._hostType, self._hostLocation, self._hostUsername, self._hostPassword ) + self._vms.append( vm ) + #TODO catch/transform exceptions + + def addTestCase( self, path ): + self._testcases.append( testcase.TestCase( path ) ) + + + def run( self ): + while True: + try: + inst = self._source.nextInstaller() + if inst == None: + print( "** Installer source returned None, aborting" ) + return + if inst.error: + raise ControlException( inst.error ) + print( "** New installer: {0}".format( inst.path ) ) + self.testInstaller( inst, inst.platform ) + except KeyboardInterrupt: + raise + except: + self._reporter.reportException() + + def testInstaller( self, inst, platform ): + for vm in self._vms: + if vm.ostype() != platform: + continue + for case in self._testcases: + if not case.supportsPlatform( platform ): + continue + res = result.Result() + try: + try: + res.setInstaller( inst ) + res.setTestCase( case ) + res.setVirtualMachine( vm ) + res.testStarted() + self.run_test( inst.path, vm, case, res ) + res.testFinished() + inst.markAsTested() + except KeyboardInterrupt: + raise + except: + res.addException() + finally: + self._reporter.reportResult( res ) + + def convertCheckerResults( self, filename, res ): + parser = make_parser() + parser.setContentHandler( Handler( res ) ) + f = file( filename, 'rb' ) + parser.parse( f ) + + def run_test( self, installerPath, vm, testcase, res ): + steps = testcase.steps() + if len( steps ) == 0: + raise ControlException( "No steps found for testcase {0}".format( testcase.name() ) ) + + revertStatus, _ = vm.revertToSnapshot() + if revertStatus != 0: + raise VMException( "Failed to revert to snapshot '{0}'".format( vm.snapshot() ) ) + + + time.sleep( 5 ) # Trying to avoid a possible race between restore and start + + vm.start() + + try: + try: + vm.checkPythonInstalled() + wrapperpath = vm.copyToTemp( utils.execution_path( 'guest.py' ) ) + + for stepNum in range( len( steps ) ): + needSnapshot = False + step = steps[stepNum] + if stepNum == 0: + executableguestpath = vm.copyToTemp( installerPath ) + else: + executableguestpath = testcase.maintenanceToolLocation() + + outputFileName = 'output{0}.log'.format( stepNum ) + outputpath = vm.mkTempPath( outputFileName ) + scriptguestpath = vm.copyToTemp( step.installscript() ) + timeout = step.timeout() + checkerguestpath = vm.copyToTemp( step.checkerTestDir(), "checkerTestDir{0}".format( stepNum ) ) if len( string.strip( step.checkerTestDir() ) ) > 0 else None + vm.command( 'Execute installer', "runProgramInGuest", "'{0}' '{1}' '{2}' '{3}' '{4}' --script '{5}'".format( vm.python(), wrapperpath, outputpath, timeout, executableguestpath, scriptguestpath ) ) + vm.copyFromTemp( outputFileName, outputFileName ) + r = ConfigParser.SafeConfigParser() + r.read( outputFileName ) + try: + s = r.get( 'Result', 'ExitCode' ) + exitCode = int( s ) + except ValueError: + res.addInternalError( "Could not parse integer exit code from '{0}'".format( r.get( 'Result', 'ExitCode' ) ) ) + exitCode = -1 + try: + s = r.get( 'Result', 'ExecutionTime' ) + executionTime = float( s ) + except ValueError: + res.addInternalError( "Could not parse float execution time from '{0}'".format( r.get( 'Result', 'ExecutionTime' ) ) ) + executionTime = 0.0 + + exitStatus = result.exitStatusFromString( r.get( 'Result', 'ExitStatus' ) ) + instR = result.ExecutionResult( exitCode, exitStatus, executionTime ) + + if instR.hasError(): + needSnapshot = True + + checkerResults = [] + if checkerguestpath and not instR.hasError(): + if ( platform.system() == "Darwin" ): + # Have to sleep to work around VMware Fusion bug + time.sleep( 30 ) + run_py = vm.copyToTemp( self._checkerDir ) + vm.pathSep() + "run.py" + if ( platform.system() == "Darwin" ): + # Have to sleep to work around VMware Fusion bug + time.sleep( 30 ) + checkeroutputFileName = 'checker-output{0}.xml'.format( stepNum ) + checkeroutput = vm.mkTempPath( checkeroutputFileName ) + vm.command( 'Execute checker tests', "runProgramInGuest", "'{0}' '{1}' '{2}' -o '{3}' -p '{4}'".format( vm.python(), run_py, checkerguestpath, checkeroutput, testcase.targetDirectory() ) ) + vm.copyFromTemp( checkeroutputFileName, checkeroutputFileName ) + self.convertCheckerResults( localcheckeroutput, checkerResults ) + if res.hasCheckerErrors(): + needSnapshot = True + if self._createErrorSnapshots and needSnapshot: + snapshot = 'error-{0}-{1}'.format( datetime.datetime.now().strftime( '%Y%m%d_%H%M%S' ), utils.randomString( 4 ) ) + status, _ = vm.createSnapshot( snapshot ) + if status == 0: + res.setErrorSnapshot( snapshot ) + else: + res.addInternalError( 'Could not create error snapshot "{0}"'.format( snapshot ) ) + res.addStepResult( result.StepResult( instR, checkerResults ) ) + #TODO handle timeouts? + finally: + vm.kill() + except e: + print( e ) + res.addInternalError( str( e ) ) +
\ No newline at end of file diff --git a/test-framework/vmware/ftpsource.py b/test-framework/vmware/ftpsource.py new file mode 100644 index 000000000..306c76b63 --- /dev/null +++ b/test-framework/vmware/ftpsource.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +from ftplib import FTP +import datetime, functools, os, time, tempfile +from source import Installer +import utils + +def _timestampFromFilename( platform, fn ): + # windows: assumed format is YYYY_mm_dd_HH_MM as prefix + # linux: assumed format is YYYY-mm-dd as suffix + if platform.startswith( 'windows' ): + format = '%Y_%m_%d_%H_%M' + length = len( 'YYYY_mm_dd_HH_MM' ) + return datetime.datetime.strptime( fn[0:length], format ) + else: + format = '%Y-%m-%d' + length = len( 'YYYY-mm-dd' ) + return datetime.datetime.strptime( fn[-length:], format ) + + + +def timestampFromFilename( platform, fn ): + try: + return _timestampFromFilename( platform, fn ) + except ValueError: + return None + +def filesNewerThan( files, dt ): + if dt: + return filter( lambda (x,y): y >= dt, files ) + else: + return files + +#internal to FtpSource +class Location: + def __init__( self, host, path, platform ): + self.host = host + self.path = path + self.platform = platform + self.testedFiles = [] + + def ls( self ): + ftp = FTP( self.host ) + ftp.login() + try: + return map( utils.basename, ftp.nlst( self.path ) ) + finally: + ftp.close() + + def filesSortedByTimestamp( self ): + files = self.ls() + withTS = [( i, timestampFromFilename( self.platform, i ) ) for i in files] + filtered = filter( lambda (x,y): y != None, withTS ) + filtered.sort( key=lambda (x,y): y ) + return filtered + + def untestedFilesSortedByTimestamp( self ): + l = self.filesSortedByTimestamp() + return filter( lambda ( x, y ): not self.isTested( x ), l ) + + def markAsTested( self, filename ): + self.testedFiles.append( filename ) + + def isTested( self, filename ): + return filename in self.testedFiles + + def description( self ): + return "host={0} path={1} platform={2}".format( self.host, self.path, self.platform ) + + def download( self, fn, target ): + ftp = FTP( self.host ) + ftp.login() + try: + ftp.retrbinary( 'RETR {0}/{1}'.format( self.path, fn ), target.write ) + finally: + ftp.close() + +class FtpSource: + def __init__( self, delay=60*60, tempdir='/tmp' ): + self._locations = [] + self._delay = delay + self._tempdir = tempdir + self._startDate = None + + def setStartDate( self, date ): + self._startDate = date + + def addLocation( self, host, path, platform ): + self._locations.append( Location( host, path, platform ) ) + + def nextInstaller( self ): + while True: + for i in self._locations: + print( "** Checking FTP location: " + i.description() ) + files = i.untestedFilesSortedByTimestamp() + if self._startDate != None: + files = filesNewerThan( files, self._startDate ) + if len( files ) == 0: + continue; + fn, ts = files[0] + print( "** Downloading new installer: {0}...".format( fn ) ) + tmp = tempfile.NamedTemporaryFile( dir=self._tempdir, prefix=fn ) + i.download( fn, tmp ) + print( "** Download completed. ({0})".format( tmp.name ) ) + i.lastTested = ts + inst = Installer( tmp.name, i.platform, i, ts, tmp ) + inst.sourceFilename = fn + return inst + print( "** No installers found. Going to sleep for {0} seconds...".format( self._delay ) ) + time.sleep( self._delay ) + +if __name__ == "__main__": + src = FtpSource() + src.addLocation( "hegel", "/projects/ndk/installers/windows", "windows" ) + while True: + inst = src.nextInstaller() + print inst + inst.markAsTested() diff --git a/test-framework/vmware/guest.py b/test-framework/vmware/guest.py new file mode 100644 index 000000000..b410d366b --- /dev/null +++ b/test-framework/vmware/guest.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import sys, subprocess, ConfigParser, platform, os + +# begin Autobuild/helpers/runcommand.py + +import os +import re +import time +import sys +import signal +from subprocess import Popen +from threading import Thread +from subprocess import PIPE +import subprocess +import platform + +def DebugN( l, m ): + pass + +def windowskill( pid ): + """ replace os.kill on Windows, where it is not available""" + Cmd = 'taskkill /PID ' + str( int( pid ) ) + ' /T /F' + if os.system( Cmd ) == 0: + DebugN( 4, 'windowskill: process ' + str( pid ) + ' killed.' ) + +def kill( pid, signal ): + if 'Windows' in platform.platform(): + windowskill( pid ) + else: + os.kill( pid, signal ) + +class CommandRunner( Thread ): + def __init__ ( self, Cmd ): + Thread.__init__( self ) + self.__started = None + self.mCmd = Cmd + self.mOutput = () + self.mPid = -1 + self.mReturnCode = -1 + self.mError = () + self.__combinedOutput = False + + def setCombinedOutput(self, combine): + if combine: # make sure combine is usable as a boolean + self.__combinedOutput = True + else: + self.__combinedOutput = False + + def getCombineOutput(self): + return self.__combinedOutput + + def run( self ): + DebugN( 4, 'RunCommand: ' + str( self.mCmd ) ) + stderrValue = subprocess.PIPE + if self.__combinedOutput: + stderrValue = subprocess.STDOUT + self.__started = True +#nokia-sdk: changed to shell=False, as shell=True requires manual quoting of args + p = Popen ( self.mCmd, shell = False, stdout=subprocess.PIPE, stderr=stderrValue ) + self.mPid = p.pid + self.mOutput, self.mError = p.communicate() + self.mReturnCode = p.returncode + DebugN( 4, 'ReturnCode of RunCommand: ' + str( p.returncode ) ) + + def started(self): + return self.__started + + def output( self ): + return self.mOutput, self.mError + + def terminate( self ): + # FIXME logic? + if self.mPid != -1: + kill( self.mPid, signal.SIGTERM ) + if self.mPid == True: + self.join( 5 ) + kill( self.mPid, signal.SIGKILL ) + self.mPid = -1 + + +def RunCommand( Cmd, TimeOutSeconds=-1, CombineOutput = False ): + timeoutString = ' without a timeout' + if TimeOutSeconds > 0: + timeoutString = ' with timeout of ' + str( TimeOutSeconds ) + combinedOutputString = ' and separate output for stdout and stderr' + if CombineOutput: + combinedOutputString = ' and combined stdout and stderr output' + DebugN ( 3, 'RunCommand: executing ' + str( Cmd ) + timeoutString + combinedOutputString ) + runner = CommandRunner ( Cmd ) + runner.setCombinedOutput( CombineOutput ) + runner.start() + # poor man's mutex: + while not runner.started(): + time.sleep( 0.1 ) +# if "CYGWIN" in platform.platform() or 'Windows' in platform.platform(): +# time.sleep( 1 ) + if TimeOutSeconds == -1: + runner.join() + else: + runner.join( TimeOutSeconds ) + if runner.isAlive(): + runner.terminate() +# if "CYGWIN" in platform.platform() or 'Windows' in platform.platform(): +# time.sleep(1) + runner.join( 5 ) + DebugN( 3, 'RunCommand: command timed out, returncode is ' + str( runner.mReturnCode ) ) + return ( runner.mReturnCode, runner.output(), True ) + else: + DebugN( 3, 'RunCommand: command completed, returncode is ' + str( runner.mReturnCode ) ) + return ( runner.mReturnCode, runner.output(), False ) + +# end Autobuild/helpers/runcommand.py + + +def printUsage(): + print( "Usage: {0} <outputfile> <timeout> <program> [<args>*]".format( sys.argv[0] ) ) + +if len( sys.argv ) < 4: + printUsage() + sys.exit( 1 ) + +output = sys.argv[1] +timeout = int( sys.argv[2] ) +cmd = sys.argv[3:] + +start = time.clock() +exitCode, pout, timedOut = RunCommand( cmd, timeout ) +end = time.clock() + +if timedOut: + exitStatus = 'Timeout' +else: + exitStatus = 'Normal' #TODO: detect crash + +config = ConfigParser.SafeConfigParser() +config.add_section( 'Result' ) +config.set( 'Result', 'ExitCode', str( exitCode ) ) +config.set( 'Result', 'ExitStatus', exitStatus ) +config.set( 'Result', 'Filename', cmd[0] ) +config.set( 'Result', 'Arguments', " ".join( cmd[1:] ) ) +config.set( 'Result', 'Timeout', str( timeout ) ) +config.set( 'Result', 'ExecutionTime', str( end - start ) ) +config.set( 'Result', 'Stdout', pout[0] ) +config.set( 'Result', 'Stderr', pout[1] ) + +config.add_section( 'Platform' ) +config.set( 'Platform', 'system', platform.system() ) +config.set( 'Platform', 'release', platform.release() ) +config.set( 'Platform', 'version', platform.version() ) +config.set( 'Platform', 'machine', platform.machine() ) + +with open( output, 'w' ) as configFile: + config.write(configFile ) + +sys.exit( exitCode ) + + + + diff --git a/test-framework/vmware/guestconfig.py b/test-framework/vmware/guestconfig.py new file mode 100644 index 000000000..f80b1c73d --- /dev/null +++ b/test-framework/vmware/guestconfig.py @@ -0,0 +1,11 @@ +import os, ConfigParser, tempfile, platform +config = ConfigParser.SafeConfigParser() +config.add_section('Guest') +config.set('Guest', 'temp_dir', str(tempfile.gettempdir())) +config.set('Guest', 'path_sep', os.sep) +config.set('Guest', 'system', platform.system()) +config.set('Guest', 'release', platform.release()) +config.set('Guest', 'version', platform.version()) +config.set('Guest', 'machine', platform.machine()) +with open('example.cfg', 'wb') as configFile: + config.write(configFile) diff --git a/test-framework/vmware/reporter.py b/test-framework/vmware/reporter.py new file mode 100644 index 000000000..57366a8fe --- /dev/null +++ b/test-framework/vmware/reporter.py @@ -0,0 +1,96 @@ +import datetime, socket, sys, traceback +from xml.sax.saxutils import XMLGenerator +from xml.sax.xmlreader import AttributesNSImpl +import result +from result import exitStatusAsString +from xmlutils import startElement, endElement, writeElement + +class Reporter: + def __init__( self ): + pass + + def reportException( self ): + sys.stderr.write( traceback.format_exc( 15 ) + '\n' ) + + def reportResult( self, result ): + try: + self.toXml( result, sys.stdout ) + finally: + sys.stdout.flush() + + def toXml( self, result, out ): + atom = "http://www.w3.org/2005/Atom" + tf = "http://sdk.nokia.com/test-framework/ns/1.0" + + gen = XMLGenerator( out, 'utf-8' ) + gen.startDocument() + gen.startPrefixMapping( 'atom', atom) + gen.startPrefixMapping( 'tf', tf ) + + startElement( gen, atom, 'entry' ) + + writeElement( gen, atom, 'title', result.constructTitle() ) + writeElement( gen, atom, 'updated', datetime.datetime.now().isoformat() ) + + writeElement( gen, tf, 'errorSummary', exitStatusAsString( result.status() ) ) + + writeElement( gen, tf, 'host', socket.gethostname() ) + if result._testStart != None: + writeElement( gen, tf, 'testStart', result._testStart.isoformat() ) + else: + result._internalErrors.append( "Result generator: no start timestamp found." ) + + if result._testEnd != None: + writeElement( gen, tf, 'testEnd', result._testEnd.isoformat() ) + else: + result._internalErrors.append( "Result generator: no end timestamp found." ) + + + startElement( gen, tf, 'installer' ) + writeElement( gen, tf, 'sourceUrl', result._installerSourceLocation ) + writeElement( gen, tf, 'platform', result._installerTargetPlatform ) + #TODO revision + endElement( gen, tf, 'installer' ) + + if result._testcase != None: + startElement( gen, tf, 'testCase' ) + writeElement( gen, tf, 'name', result._testcase.name() ) + writeElement( gen, tf, 'path', result._testcase.path() ) + writeElement( gen, tf, 'installScript', result._testcase.installscript() ) + endElement( gen, tf, 'testCase' ) + else: + result._internalErrors.append( "Result generator: No test case given." ) + + if result._installationResult != None: + startElement( gen, tf, 'installationResult' ) + writeElement( gen, tf, 'exitCode', str( result._installationResult.exitCode ) ) + writeElement( gen, tf, 'exitStatus', exitStatusAsString( result._installationResult.exitStatus ) ) + endElement( gen, tf, 'installationResult' ) + else: + result._internalErrors.append( "Result generator: No installation result given." ) + startElement( gen, tf, 'checkerResult' ) + + for err in result._checkerErrors: + writeElement( gen, tf, 'error', err ) + endElement( gen, tf, 'checkerResult' ) + + startElement( gen, tf, 'virtualMachine' ) + writeElement( gen, tf, 'path', result._vm.vmxPath() ) + writeElement( gen, tf, 'platform', result._vm.ostype() ) + writeElement( gen, tf, 'snapshot', result._vm.snapshot() ) + endElement( gen, tf, 'virtualMachine' ) + + startElement( gen, tf, 'internalErrors' ) + for i in result._internalErrors: + writeElement( gen, tf, 'internalError', str( i ) ) + + endElement( gen, tf, 'internalErrors' ) + + if result._errorSnapshot != None: + writeElement( gen, tf, 'errorSnapshot', result._errorSnapshot ) + + endElement( gen, atom, 'entry' ) + + gen.endDocument() + + diff --git a/test-framework/vmware/result.py b/test-framework/vmware/result.py new file mode 100644 index 000000000..07d4832aa --- /dev/null +++ b/test-framework/vmware/result.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +import control, testcase, datetime, source, tempfile, traceback, utils +from source import Installer + +class ExecutionResult: + #return codes: + Success = 0 + InstallationFailed = 1 + InstallationCanceled = 2 + + #exit status: + Normal = 0 + Crash = 1 + Timeout = 2 + + def __init__( self, exitCode, exitStatus, executionTime ): + self.exitCode = exitCode + self.exitStatus = exitStatus + self.executionTime = executionTime + + def hasError( self ): + return self.exitCode != 0 or self.exitStatus != ExecutionResult.Normal + +class CheckerResult: + Passed = 0 + Failed = 1 + NotRun = 2 + + def hasError( list ): + #the following would more efficient with something find_if-like (stopping if test with error is found) + return len( [x for i in list if i.hasError() ] ) > 0 + + def __init__( self, name, result, errorString ): + self.name = utils.randomString( 3 ) + '_' + name + self.result = result + self.errorString = errorString + + def hasError( self ): + return self.result != CheckerResult.Passed + +class StepResult: + def __init__( self, executionResult, checkerResults ): + self.executionResult = executionResult + self.checkerResults = checkerResults + + def hasCheckerErrors( self ): + return CheckerResult.hasError( self.checkerResults ) + +def exitStatusFromString( s ): + if s == 'Crash': + return ExecutionResult.Crash + if s == 'Timeout': + return ExecutionResult.Timeout + if s == 'Normal': + return ExecutionResult.Normal + raise control.ControlException( "Unknown exit status string: {0}".format( s ) ) + +#there is probably a cooler way with introspection and stuff: +def exitStatusAsString( status ): + if status == ExecutionResult.Crash: + return 'Crash' + if status == ExecutionResult.Normal: + return 'Normal' + if status == ExecutionResult.Timeout: + return 'Timeout' + raise control.ControlException( "Unknown exit status: {0}".format( status ) ) + +class Result: + + NoError=0 # Everything ok + InstallerError=1 #Installer failed, or post-conditions not met + InternalError=2 #Internal test framework error + + def __init__( self ): + self._internalErrors = [] + self._stepResults = [] + self._installer = None + self._testStart = None + self._testEnd = None + self._testcase = None + self._vm = None + self._errorSnapshot = None + self._revision = "revision-todo" + + def setInstaller( self, installer ): + self._installer = installer + + def testStarted( self ): + self._testStart = datetime.datetime.now() + + def testFinished( self ): + self._testEnd = datetime.datetime.now() + + def setTestCase( self, testcase ): + self._testcase = testcase + + def setErrorSnapshot( self, name ): + self._errorSnapshot = name + + def setVirtualMachine( self, vm ): + self._vm = vm + + def addInternalError( self, errstr ): + self._internalErrors.append( errstr ) + + def hasInternalErrors( self ): + return len( self._internalErrors ) > 0 + + def addException( self ): + s = 'Unexpected exception: {0}'.format( traceback.format_exc( 15 ) ) + self._internalErrors.append( s ) + + def hasCheckerErrors( self ): + for step in self._stepResults: + if CheckerResult.hasError( step.checkerResults ): + return True + return False + + + def addStepResult( self, result ): + self._stepResults.append( result ) + + def status( self ): + if len( self._internalErrors ) > 0: + return Result.InternalError + if len( self._stepResults ) == 0: + return Result.InternalError + for step in self._stepResults: + if step.executionResult.exitCode != ExecutionResult.Success or step.exitStatus != ExecutionResult.Normal: + return Result.InstallerError + if step.hasCheckerErrors(): + return Result.InstallerError + #TODO: check test results + return Result.NoError + + def statusAsNiceString( self ): + s = self.status() + if s == Result.InternalError: + return "Internal framework errors" + if s == Result.InstallerError: + return "Installation error" + return "OK" + + def constructTitle( self ): + smiley = ":-)" if self.status() == Result.NoError else ":-(" + + return "{0}: {1} on {2} testing {3} - {4} {5}".format( self._revision, utils.basename( self._installerSourceLocation ), self._vm.name(), self._testcase.name(), self.statusAsNiceString(), smiley ) +
\ No newline at end of file diff --git a/test-framework/vmware/run-test.py b/test-framework/vmware/run-test.py new file mode 100644 index 000000000..c063f3e70 --- /dev/null +++ b/test-framework/vmware/run-test.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +import sys, os, ConfigParser, optparse, time, virtualmachine, utils + +# TODO: - Look in sensible locations for python in the guest VM +# - Try and work-around VMware Fusion needing to be closed + + +def check_option( option, optionName ): + if option: + return + print("** Could not find a value for {0}".format(optionName) ) + print("** Please specify it on the commandline or configuration file.") + print + optionParser.print_help(); + sys.exit(-1) + +def die(message): + print "** " + str(message) + sys.exit(1); + +# +# Parse options +# + +optionParser = optparse.OptionParser(usage="%prog [options] vmx-file", version="%prog 0.9") +optionParser.add_option("-u", "--username", dest="username", help="username for VM", metavar="USERNAME" ) +optionParser.add_option("-p", "--password", dest="password", help="password for VM", metavar="PASSWORD" ) +optionParser.add_option("-S", "--script", dest="script", help="script for VM", metavar="SCRIPT" ) +optionParser.add_option("-s", "--snapshot", dest="snapshot", help="snapshot for VM", metavar="SNAPSHOT" ) +optionParser.add_option("-P", "--python", dest="python", help="python location in VM", metavar="PYTHON" ) +optionParser.add_option("-v", "--vmrun", dest="vmrun", help="vmrun command for VM", metavar="VMRUN") +optionParser.add_option("-c", "--config", dest="config", help="configuration file for VM", metavar="CONFIG") +optionParser.add_option("-i", "--installer", dest="installer", help="installer executable to command inside the VM", metavar="INSTALLER") +optionParser.add_option("-e", "--installscript", dest="installscript", help="QtScript script to use for non-interactive installation", metavar="INSTALLSCRIPT") + +(options, args) = optionParser.parse_args() + +try: + options.vmx = args[0] +except IndexError: + options.vmx = None + +options.ostype = None +options.guestTempDir = None + +config = ConfigParser.SafeConfigParser() +if options.config: + print("** Reading config: " + options.config ) + config.read( options.config ) +else: + print("** No config given") + +options.username = utils.get_config_option( config, options.username, "username", "nokia" ) +options.password = utils.get_config_option( config, options.password, "password", "nokia" ) +options.script = utils.get_config_option( config, options.script, "script", "guest.py" ) +options.python = utils.get_config_option( config, options.python, "python", "c:/python26/python.exe" ) +options.snapshot = utils.get_config_option( config, options.snapshot, "snapshot", "base" ) +options.vmrun = utils.get_config_option( config, options.vmrun, "vmrun" ) +options.vmx = utils.get_config_option( config, options.vmx, "vmx" ) +options.guestTempDir = utils.get_config_option( config, options.guestTempDir, "tempDir", "c:\\windows\\temp" ) +options.ostype = utils.get_config_option( config, options.ostype, "os", "windows" ) + +check_option( options.vmx, "vmx-file" ) + +# Search the PATH and the a few extra locations for 'vmrun' +if not options.vmrun: + options.vmrun = utils.findVMRun() + +check_option( options.vmrun, "VMRUN" ) +check_option( options.installer, "INSTALLER" ) +check_option( options.installscript, "INSTALLSCRIPT" ) + +# +# VM actions +# + +vm = virtualmachine.VirtualMachine( options.vmrun, options.vmx, options.username, options.password, options.guestTempDir, options.ostype ) + +snapshotExists = vm.snapshotExists( options.snapshot ) +if not snapshotExists: + die("Could not find '{0}' snapshot, please create it in the VM.".format( options.snapshot ) ) + +revertStatus, _ = vm.revertToSnapshot( options.snapshot ) +if revertStatus != 0: + die("Failed to revert to snapshot") + +time.sleep( 5 ) # Trying to avoid a possible race between restore and start + +vm.start() + +try: + pythonStatus, _ = vm.command("Checking for guest installed Python", "fileExistsInGuest", options.python) + if pythonStatus != 0: + raise virtualmachine.VMException("Please install Python in the VM from http://www.python.org/download/") + + wrapperpath = vm.copyToTemp( 'guest.py' ) + installerpath = vm.copyToTemp( options.installer ) + scriptpath = vm.copyToTemp( options.installscript ) + outputpath = vm.mkTempPath( 'output.log' ) + vm.command( 'Execute installer', "runProgramInGuest", "-interactive -activeWindow '{0}' '{1}' '{2}' '{3}' --script '{4}'".format( options.python, wrapperpath, outputpath, installerpath, scriptpath ) ) + + vm.copyFromTemp( 'output.log', 'output.log' ) + result = ConfigParser.SafeConfigParser() + result.read( 'output.log' ) + print( "Installer exit code: " + result.get( 'Result', 'ExitCode' ) ) + #TODO parse output and do something with it + +except virtualmachine.VMException as exception: + die( exception ) +finally: + vm.kill() diff --git a/test-framework/vmware/run.py b/test-framework/vmware/run.py new file mode 100644 index 000000000..f02571051 --- /dev/null +++ b/test-framework/vmware/run.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import ConfigParser, datetime, optparse, os, sys +from functools import partial +import cdashreporter, control, ftpsource, source, reporter, utils + +def die( msg ): + sys.stderr( msg + '\n' ) + sys.exit( 1 ) + +optionParser = optparse.OptionParser(usage="%prog [options] configfile installer0 [installer1 ...]", version="%prog 0.1") +optionParser.add_option("-r", "--vmrun", dest="vmrun", help="vmrun executable to use", metavar="VMRUN" ) +optionParser.add_option("-s", "--only-since", dest="since", help="test only installers newer than timestamp (YYYY-MM-DD or YYYY-MM-DD-hh-mm)", metavar="SINCE" ) +optionParser.add_option("-c", "--checkerInstallation", dest="checkerInstallation", help="checker installation to use for post-installation checks", metavar="CHECKERINSTALLATION" ) +(options, args) = optionParser.parse_args() + +try: + configpath = utils.makeAbsolutePath( args[0], os.getcwd() ) +except IndexError: + optionParser.print_usage( sys.stderr ) + sys.exit( 1 ) + +config = ConfigParser.SafeConfigParser() +config.read( configpath ) + +#make an unary functor to create absolute paths +abspath = partial( utils.makeAbsolutePath, relativeTo=os.path.dirname( configpath ) ) + +vmrun = utils.get_config_option( config, options.vmrun, "vmrun", utils.findVMRun() ) +useGui = utils.get_config_option( config, None, "gui", "true" ).lower() == "true" +createErrorSnapshots = utils.get_config_option( config, None, "createErrorSnapshots", "true" ).lower() == "true" +hostType = utils.get_config_option( config, None, "type", "", "Host" ) +hostLocation = utils.get_config_option( config, None, "location", "", "Host" ) +hostUsername = utils.get_config_option( config, None, "username", "", "Host" ) +hostPassword = utils.get_config_option( config, None, "password", "", "Host" ) + +cdashHost = utils.get_config_option( config, None, "host", "", "CDash" ) +cdashLocation = utils.get_config_option( config, None, "location", "", "CDash" ) +cdashProject = utils.get_config_option( config, None, "project", "", "CDash" ) + +if vmrun == None: + die( "Could not find vmrun executable. Please specify it in the config file (vmrun=...) or via the --vmrun option" ) + +checkerInstallation = utils.get_config_option( config, options.checkerInstallation, "checkerInstallation" ) +if checkerInstallation == None: + die( "Could not find checker installation. Please specify it in the config file (checkerInstallation=...) or via the --checkerInstallation option" ) + +#apply functor to list to get absolute paths: +testcases = map( abspath, utils.get_enumerated_config_option( config, 'testcase' ) ) +if len( testcases ) == 0: + die( "No testcases specified. Please specify at least one test case in the configuration" ) + +vms = map( abspath, utils.get_enumerated_config_option( config, 'vm' ) ) +if len( vms ) == 0: + die( "No VMs specified. Please specify at least one VM in the configuration" ) + +installers = args[1:] + +if len( installers ) > 0: + source = source.Source() + for i in installers: + source.addDummy( 5, i ) +else: + source = ftpsource.FtpSource() + if options.since: + try: + sdt = datetime.datetime.strptime( options.since, '%Y-%m-%d' ) + except ValueError: + sdt = datetime.datetime.strptime( options.since, '%Y-%m-%d-%H-%M' ) + source.setStartDate( sdt ) + found = True + nextsec = 0 + while found: + sec = "Source{0}".format( nextsec ) + nextsec += 1 + try: + host = config.get( sec, "host" ) + path = config.get( sec, "path" ) + platform = config.get( sec, "platform" ) + print( "** Add FTP location {0}:{1} ({2})".format( host, path, platform ) ) + source.addLocation( host, path, platform ) + except ConfigParser.NoSectionError: + found = False + +cdashHost = utils.get_config_option( config, None, "host", "", "CDash" ) +cdashLocation = utils.get_config_option( config, None, "location", "", "CDash" ) +cdashProject = utils.get_config_option( config, None, "project", "", "CDash" ) + +if len( cdashHost ) > 0: + reporter = cdashreporter.CDashReporter( cdashHost, cdashLocation, cdashProject ) +else: + reporter = reporter.Reporter() + +control = control.Control( vmrun, checkerInstallation, source, reporter ) +control.setGuiEnabled( useGui ) +control.setCreateErrorSnapshots( createErrorSnapshots ) + +if len( hostType ) > 0: + control.setRemoteHost( hostType, hostLocation, hostUsername, hostPassword ) + +for i in vms: + control.addVM( i ) +for i in testcases: + control.addTestCase( i ) + +control.run() diff --git a/test-framework/vmware/source.py b/test-framework/vmware/source.py new file mode 100644 index 000000000..fe067ab4e --- /dev/null +++ b/test-framework/vmware/source.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +import os, time + +class Installer: + def __init__( self, path, platform, location, timestamp=None, tempfile=None ): + self.path = path + self.platform = platform + self.error = None + self.timestamp = timestamp + self.tempfile = tempfile + self.sourceLocation = location + + def markAsTested( self ): + self.sourceLocation.markAsTested( self.sourceFilename ) + +class Source: + def __init__( self ): + self._dummies = [] + + def nextInstaller( self ): + if len( self._dummies ) == 0: + return None + delay, path = self._dummies.pop() + time.sleep( delay ) + if os.path.exists( path ): + inst = Installer( path, "linux", None ) + inst.sourceFilename = path + return inst + else: + inst = Installer( None, None ) + #simulating download errors + inst.error = "Installer '{0}' does not exist".format( path ) + return inst + + def addDummy( self, delay, path ): + self._dummies.insert( 0, ( delay, path ) ) diff --git a/test-framework/vmware/testcase.py b/test-framework/vmware/testcase.py new file mode 100644 index 000000000..65fd83880 --- /dev/null +++ b/test-framework/vmware/testcase.py @@ -0,0 +1,70 @@ +import ConfigParser, os, utils + +class Step: + def __init__( self, installscript, checkerTestDir, timeout ): + self._installscript = installscript + self._checkerTestDir = checkerTestDir + self._timeout = timeout + + def installscript( self ): + return self._installscript + + def checkerTestDir( self ): + return self._checkerTestDir + + def timeout( self ): + return self._timeout + +class TestCase: + def __init__( self, path ): + self._steps = [] + config = ConfigParser.SafeConfigParser() + config.read( path ) + self._path = path + self._platforms = [] + found = True + stepNum = 0 + while found: + sec = "Step{0}".format( stepNum ) + if not config.has_section( sec ): + found = False + continue + + stepNum += 1 + installscript = utils.makeAbsolutePath( utils.get_config_option( config, None, "installscript", None, sec ), os.path.dirname( path ) ) + checkerTestDir = utils.get_config_option( config, None, "checkerTestDir", None, sec ) + checkerTestDir = utils.makeAbsolutePath( checkerTestDir, os.path.dirname( path ) ) if checkerTestDir else "" + timeout = int( utils.get_config_option( config, None, "timeout", 60 * 60, sec ) ) + self._steps.append( Step( installscript, checkerTestDir, timeout ) ) + + self._name = utils.get_config_option( config, None, "name", utils.basename( path ) ) + self._targetDirectory = utils.get_config_option( config, None, "targetDirectory", "" ) + self._maintenanceToolLocation = utils.get_config_option( config, None, "maintenanceToolLocation", "" ) + platforms = utils.get_config_option( config, None, "platforms" ) + if platforms != None: + self._platforms = platforms.split( ',' ) + + def supportsPlatform( self, platform ): + return platform in self._platforms + + def installerTimeout( self ): + return self._installerTimeout + + def platforms( self ): + return self._platforms + + def name( self ): + return self._name + + def targetDirectory( self ): + return self._targetDirectory + + def steps( self ): + return self._steps + + def path( self ): + return self._path + + def maintenanceToolLocation( self ): + return self._maintenanceToolLocation +
\ No newline at end of file diff --git a/test-framework/vmware/utils.py b/test-framework/vmware/utils.py new file mode 100644 index 000000000..24529be40 --- /dev/null +++ b/test-framework/vmware/utils.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +import ConfigParser, inspect, os, string, sys +from random import Random + +def findVMRun(): + searchDirectories = os.environ['PATH'].split(os.pathsep) + searchDirectories.append("/Library/Application Support/VMware Fusion") + for directory in searchDirectories: + possibleFile = os.path.join(directory, 'vmrun') + if os.path.isfile(possibleFile): + return possibleFile + return None + +def basename( path ): + if path.endswith( os.path.sep ): + return os.path.basename( path[0,-1] ) + else: + return os.path.basename( path ) + +def makeAbsolutePath( path, relativeTo ): + if os.path.isabs( path ) or relativeTo == None: + return path + else: + return relativeTo + os.sep + path + +def execution_path( filename ): + return os.path.join( os.path.dirname( inspect.getfile( sys._getframe( 1 ) ) ), filename ) + +def unixPathSep( s ): + return s.replace( '\\', '/' ) + +def get_config_option( config, initial, option, default=None, section="General" ): + # If there's already a valid option from the commandline, + # override the value from the configuration file. + if initial != None: + print( "** Overridden {0}={1}".format( option, initial ) ) + return initial + try: + tmp = config.get( section, option ) + print( "** Read config value {0}={1}".format( option, tmp ) ) + return tmp + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): + print( "** Using default value {0}={1}".format( option, default ) ) + return default + +def get_enumerated_config_option( config, option ): + res = [] + i = 0 + while True: + key = option + str( i ) + val = get_config_option( config, None, key ) + if val == None: + print res + return res + res.append( val ) + i += 1 + +def randomString( length ): + return ''.join( Random().sample( string.letters + string.digits, length ) ) diff --git a/test-framework/vmware/virtualmachine.py b/test-framework/vmware/virtualmachine.py new file mode 100644 index 000000000..7507cd531 --- /dev/null +++ b/test-framework/vmware/virtualmachine.py @@ -0,0 +1,177 @@ +# -*- coding: utf-8 -*- +import commands, ConfigParser, os, string, utils, platform + +class VMException(Exception): + def __init__(self, value): + self.value = value + def __str__(self): + return repr(self.value) + +class VirtualMachine: + def __init__( self, vmrun, vmx, username, password, tempDir, ostype ): + self._vmrun = vmrun + self._vmx = vmx + self._username = username + self._password = password + self._tempDir = tempDir + self._ostype = ostype + self._useGui = True + self._name = "no name" + self._hostType = "" + self._hostLocation = None + self._hostUsername = None + self._hostPassword = None + + def setGuiEnabled( self, usegui ): + self._useGui = usegui + + def name( self ): + return self._name + + def isRemote( self ): + return len( self._hostType ) > 0; + + def setRemoteHost( self, type, loc, user, pw ): + self._hostType = type + self._hostLocation = loc + self._hostUsername = user + self._hostPassword = pw + + + def command( self, message, command, parameters=None ): + if len( self._hostType ) > 0: + remote = "-T {0} -h '{1}' -u {2} -p {3}".format( self._hostType, self._hostLocation, self._hostUsername, self._hostPassword ) + else: + remote = "" + vmcommand = "'{0}' {1} -gu {2} -gp {3} {4} '{5}' ".format( self._vmrun, remote, self._username, self._password, command, self._vmx ) + if parameters: + vmcommand += parameters + print "** " + message + status, output = commands.getstatusoutput(vmcommand) + if status != 0: + print "* Return code: {0}".format(status) + print "* Output:" + print output + print + return status, output + + def copyFileToGuest( self, source, target ): + #TODO convert paths to guest system? + status, _ = self.command( 'Copying {0} to {1}'.format( source, target ), 'copyFileFromHostToGuest', "'{0}' '{1}'".format( source, target ) ) + if status != 0: + raise VMException( "Could not copy from host to guest: source {0} target {1}".format( source, target ) ) + + def copyFileFromGuest( self, source, target ): + #TODO convert paths to guest system? + status, _ = self.command( 'Copying {0} to {1}'.format( source, target ), 'copyFileFromGuestToHost', "'{0}' '{1}'".format( source, target ) ) + if status != 0: + raise VMException( "Could not copy from guest to host: source {0} target {1}".format( source, target ) ) + + + def ostype( self ): + return self._ostype + + def vmxPath( self ): + return self._vmx + + def mkTempPath( self, filename ): + return self._tempDir + self.pathSep() + utils.basename( filename ) + + def pathSep( self ): + if self._ostype == "windows": + return "\\" + else: + return "/" + + def copyToTemp( self, source, targetN=None ): + if source == None or len( string.strip( source ) ) == 0: + return None + if targetN == None: + targetN = source + + target = self.mkTempPath( targetN ) + self.copyFileToGuest( source, target ) + return target + + def copyFromTemp( self, filename, target ): + source = self.mkTempPath( filename ) + self.copyFileFromGuest( source, target ) + return target + + def start( self ): + arg = "gui" if self._useGui else "nogui" + self.command("Starting VM ({0})".format( arg ), "start", arg ) + + def kill( self ): + self.command("Stopping VM", "stop", "hard" ) + + def isRunning( self ): + _, vmList = self.command("Checking running VMs", "list") + _, _, vmFilename = self._vmx.rpartition( os.sep ) + return vmList.find(vmFilename) is not -1 + + def snapshotExists( self, snapshot ): + _, output = self.command("Checking snapshots", "listSnapshots") + snapshotList = output.split("\n") + # Remove first entry which contains number of snapshots + snapshotList.pop(0) + return snapshotList.count( snapshot ) > 0 + + def checkPythonInstalled( self ): + pp = utils.unixPathSep( self._python ) + pythonStatus, _ = self.command("Checking for guest installed Python", "fileExistsInGuest", pp ) + print pythonStatus + if pythonStatus != 0: + raise VMException("Could not find python in {0}: Please specify the path/install Python in the VM from http://www.python.org/download/".format( pp ) ) + else: + print("Python found ({0})".format( pp ) ) + + def python( self ): + return self._python + + def snapshot( self ): + return self._snapshot + + def revertToSnapshot( self, snapshot=None ): + if snapshot != None: + snap = snapshot + else: + snap = self._snapshot + # VMware Fusion needs to be closed before you can restore snapshots so kill it + if ( platform.system() == "Darwin" ): + commands.getstatusoutput( "ps x|grep 'VMware Fusion'|cut -d ' ' -f1|xargs kill" ) + return self.command("Reverting to '{0}' snapshot".format( snap ), "revertToSnapshot", snap ) + + def createSnapshot( self, name ): + return self.command("Creating error snapshot '{0}'".format( name ), "snapshot", name ) + + +def fromVMRunAndPath( vmrun, path ): + config = ConfigParser.SafeConfigParser() + config.read( path ) + + hostType = utils.get_config_option( config, None, "type", "", "Host" ) + hostLocation = utils.get_config_option( config, None, "location", "", "Host" ) + hostUsername = utils.get_config_option( config, None, "username", "", "Host" ) + hostPassword = utils.get_config_option( config, None, "password", "", "Host" ) + + vmxVal = utils.get_config_option( config, None, "vmx" ) + if not len( hostType ) == 0: + vmx = utils.makeAbsolutePath( vmxVal, os.path.dirname( path ) ) + else: + vmx = vmxVal + + username = utils.get_config_option( config, None, "username", "nokia" ) + password = utils.get_config_option( config, None, "password", "nokia" ) + tempDir = utils.get_config_option( config, None, "tempDir", "c:\\windows\\temp" ) + ostype = utils.get_config_option( config, None, "os", "windows" ) + + vm = VirtualMachine( vmrun, vmx, username, password, tempDir, ostype ) + vm._name = utils.get_config_option( config, None, "name", utils.basename( path ) ) + vm._snapshot = utils.get_config_option( config, None, "snapshot", "base" ) + vm._python = utils.get_config_option( config, None, "python", "c:/python26/python.exe" ) + vm._hostType = hostType + vm._hostLocation = hostLocation + vm._hostUsername = hostUsername + vm._hostPassword = hostPassword + return vm diff --git a/test-framework/vmware/xmlutils.py b/test-framework/vmware/xmlutils.py new file mode 100644 index 000000000..609412309 --- /dev/null +++ b/test-framework/vmware/xmlutils.py @@ -0,0 +1,13 @@ +def startElement( gen, ns, name, attrsb={} ): + attrs = {} + for i in attrsb: + attrs[(None, i)] = attrsb[i] + gen.startElementNS( ( ns, name ), name, attrs ) + +def endElement( gen, ns, name ): + gen.endElementNS( ( ns, name ), name ) + +def writeElement( gen, ns, name, text, attrs={} ): + startElement( gen, ns, name, attrs ) + gen.characters( text ) + endElement( gen, ns, name ) |