Repository: cloudstack Updated Branches: refs/heads/master 10391c9b4 -> 6bd5041ff
Adding first cut(draft) for marvincli Signed-off-by: Santhosh Edukulla <santhosh.eduku...@gmail.com> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/6bd5041f Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/6bd5041f Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/6bd5041f Branch: refs/heads/master Commit: 6bd5041ff01bb58c31ce3e76b1ea4a01b87998a8 Parents: 10391c9 Author: Santhosh Edukulla <santhosh.eduku...@gmail.com> Authored: Mon Sep 1 17:55:51 2014 +0530 Committer: Santhosh Edukulla <santhosh.eduku...@gmail.com> Committed: Mon Sep 1 18:00:20 2014 +0530 ---------------------------------------------------------------------- tools/marvin/marvin/deployAndRun.py | 485 ++++++++++++++++++++-------- tools/marvin/marvin/marvinInit.py | 20 +- tools/marvin/marvin/tcExecuteEngine.py | 65 ++-- tools/marvin/setup.py | 3 +- 4 files changed, 397 insertions(+), 176 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6bd5041f/tools/marvin/marvin/deployAndRun.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/deployAndRun.py b/tools/marvin/marvin/deployAndRun.py index 9f392e5..611e167 100644 --- a/tools/marvin/marvin/deployAndRun.py +++ b/tools/marvin/marvin/deployAndRun.py @@ -15,153 +15,362 @@ # specific language governing permissions and limitations # under the License. -from .tcExecuteEngine import TestCaseExecuteEngine import sys import os -import traceback -import time -from argparse import ArgumentParser -from .marvinInit import MarvinInit +from optparse import OptionParser +import cmd +import random +from marvin.marvinInit import MarvinInit +from marvin.deployDataCenter import DeployDataCenters +from marvin.cloudstackException import GetDetailExceptionInfo +from marvin.codegenerator import CodeGenerator from marvin.codes import (SUCCESS, FAILED, - EXCEPTION, - UNKNOWN_ERROR + EXCEPTION ) +from marvin.tcExecuteEngine import TestCaseExecuteEngine -parser = None - - -def printAndExit(): - ''' - Prints pretty message for parser and exit - ''' - global parser - if parser is not None: - parser.print_usage() - exit(1) - - -def parseAndCheck(): - ''' - Parses,reads the options and verifies for the config file - ''' - global parser - parser = ArgumentParser() - - parser.add_argument("-d", "--tcpath", dest="tcpath", - help="the test case directory or file path") - parser.add_argument("-c", "--config", action="store", - default="./datacenterCfg", dest="config", - help="the path where the json config file generated,\ - by default is ./datacenterCfg") - parser.add_argument("-l", "--load", dest="load", action="store_true", - help="only load config, do not deploy,\ - it will only run testcase") - parser.add_argument("-n", "--num", dest="number", - help="how many times you want to run the tests") - - options = parser.parse_args() - cfg_file = options.config - tc_path = options.tcpath - load_flag = options.load - num_iter = 1 if options.number is None else int(options.number) - - ''' - Check if the config file is None or not and exit accordingly - ''' - if cfg_file is None: - printAndExit() - return {"cfg_file": cfg_file, - "load_flag": load_flag, - "tc_path": tc_path, - "num_iter": num_iter} - - -def startMarvin(cfg_file, load_flag): - ''' - Initialize the Marvin - ''' - try: - obj_marvininit = MarvinInit(cfg_file, load_flag) - if obj_marvininit.init() == SUCCESS: - testClient = obj_marvininit.getTestClient() - tcRunLogger = obj_marvininit.getLogger() - parsedConfig = obj_marvininit.getParsedConfig() - debugStream = obj_marvininit.getDebugFile() - return {"tc_client": testClient, - "tc_runlogger": tcRunLogger, - "tc_parsedcfg": parsedConfig, - "tc_debugstream": debugStream} - else: - print "\nMarvin Initialization Failed" - exit(1) - except Exception as e: - print "\n Exception occurred while starting Marvin %s" % str(e) - exit(1) - - -def runTCs(num_iter, inp1, inp2): - ''' - Run Test Cases based upon number of iterations - ''' - n = 0 - while(n < num_iter): - engine = TestCaseExecuteEngine(inp2["tc_client"], - inp2["tc_parsedcfg"], - inp2["tc_runlogger"], - inp2["tc_debugstream"]) - if inp1["tc_file"] is not None: - engine.loadTestsFromFile(inp1["tc_file"]) - else: - engine.loadTestsFromDir(inp1["tc_dir"]) +class MarvinCliHelp(object): + + @classmethod + def do_printhelp(cls): + print "\n1. for building marvin from spec file and installing." + cls.help_build_and_install() + print "\n2. for syncing apis and installing marvin." + cls.help_sync_and_install() + print "\n3. for deploying a datacenter" + cls.help_deploydc() + print "\n4. for running test cases" + cls.help_runtest() + print "\n5. for deploying a datacenter (and) running tests" + cls.help_deploydc_and_runtest() + print "\n6. for generating apis from spec file" + cls.help_generateapis_from_apispecfile() + print "\n7. for generating apis from end point" + cls.help_generateapis_from_endpoint() + print "\n8. for printing marvincli version" + cls.help_printversion() + + @classmethod + def print_msg(cls, msg): + print ShellColor.BOLD + ShellColor.RED + msg + ShellColor.END + + @classmethod + def help_printversion(cls): + cls.print_msg("marvincli -v or --version") + + @classmethod + def help_deploydc(cls): + cls.print_msg( + "marvincli [deploydc] \n\t[config-file=<marvin-config-file EX: advanced.cfg file>]") + + @classmethod + def help_deploydc_and_runtest(cls, deploy=False): + msg = "marvincli [deploydc_and_runtest] \n\t[config-file=<path_to_marvin_cfg> \n\ttc-path=<test suite or test suite folder path>" \ + "\n\tzone=<name of the zone> \n\thyp-type=<hypervisor_type> " \ + "\n\trequired_hardware=<true\\false>]" + cls.print_msg(msg) + + @classmethod + def help_generateapis_from_apispecfile(cls): + cls.print_msg( + "marvincli [generateapis_from_apispecfile] \n\t[cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tapi-spec-file=<api spec file EX: /etc/cloud/cli/commands.xml>]") + + @classmethod + def help_generateapis_from_endpoint(cls): + cls.print_msg( + "marvincli [generateapis_from_endpoint] \n\t[cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tend-point=<CS Endpoint ip EX: localhost>]") + + @classmethod + def help_runtest(cls): + cls.print_msg( + "marvincli [runtest] \n\t[config-file=<path_to_marvin_config> \n\ttc-path=test/integration/smoke \n\trequired_hardware=<true\\false> \n\tzone=<name of zone> \n\thyp-type=<xenserver\\kvm\\vmware> etc]") + + @classmethod + def help_sync_and_install(cls): + cls.print_msg( + "marvincli [sync_and_install] \n\t[cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tend-point=<CS installed host ip EX: localhost>]") + + @classmethod + def help_build_and_install(cls): + cls.print_msg( + "marvincli [build_and_install] \n\t[cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tapi-sync-file<api spec file generated by cs EX: /etc/cloud/cli/commands.xml>]") + + +class VerifyAndExit(object): + + def __init__(self, msg): + self.msg = msg + + def __call__(self, original_func): + def new_function(*args, **kwargs): + exit_check = False + try: + if original_func(*args, **kwargs) == FAILED: + exit_check = True + except Exception as e: + print "---", e + exit_check = True + finally: + if exit_check: + print "==== %s ====" % self.msg + MarvinCliHelp.do_printhelp() + sys.exit(1) + return new_function + + +class MarvinCliCommands(object): + cmds_info = {'deploydc': {'options': ['config-file'], 'help': 'config-file=<marvin-config-file EX: advanced.cfg file>'}, + 'deploydc_and_runtest': {'options': ['config-file', 'tc-path', 'zone', 'hyp-type', 'required_hardware'], 'help': ''}, + 'generateapis_from_endpoint': {'options': '', 'help': '[cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tend-point=<CS Endpoint ip EX: localhost>]'}, + 'generateapis_from_apispecfile': {'options': '', 'help': '[cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tapi-spec-file=<api spec file EX: /etc/cloud/cli/commands.xml>]'}, + 'runtest': {'options': '', 'help': '[config-file=<path_to_marvin_config> \n\ttc-path=test/integration/smoke \n\trequired_hardware=<true\\false> \n\tzone=<name of zone> \n\thyp-type=<xenserver\\kvm\\vmware> etc]'}, + 'sync_and_install': {'options': ['sync_and_install'], 'help': '[marvincli sync_and_install cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tend-point=<CS installed host ip EX: localhost>]'}, + 'build_and_install': {'options': ['build_and_install'], 'help': '[marvincli build_and_install cs-folder-path=<cloudstack code root dir EX: /root/cs-4.5/cloudstack/> \n\tapi-sync-file<api spec file generated by cs EX: /etc/cloud/cli/commands.xml>]'}, + 'version': {'options': '', 'help': ''} + } + + +class ShellColor(object): + BLUE = '\033[94m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + END = '\033[0m' + ITALICS = '\x1B[3m' + +#VERSION = "4.5.0-SNAPSHOT" + + +class MarvinCli(cmd.Cmd, object): + + def __init__(self): + self.__configFile = None + self.__deployFlag = False + self.__zone = None + self.__hypervisorType = None + self.__tcPath = None + self.__testClient = None + self.__tcRunLogger = None + self.__parsedConfig = None + self.__resultStream = None + self.__logFolderPath = None + self.__testRunner = None + self.__requiredHw = False + self.__csFolder = "." + cmd.Cmd.__init__(self) + + @VerifyAndExit("Invalid input options, please check") + def parse_input_deploy(self, inputs=None): + ''' + Parses,reads the options and verifies for the config file + ''' + if inputs: + out_dict = {} + args = inputs.strip().split(' ') + for item in args: + (key, value) = item.split('=') + out_dict[key] = value + self.__configFile = out_dict.get('config-file', '') + if not self.__configFile: + return FAILED + print "\n==== Parsing Input Options Successful ====" + return SUCCESS + return FAILED + + @VerifyAndExit("Invalid input options, please check") + def parse_input_runtcs(self, inputs): + ''' + Parses,reads the options and verifies for the config file + ''' + if inputs: + out_dict = {} + args = inputs.strip().split(' ') + for item in args: + (key, value) = item.split('=') + out_dict[key] = value + self.__configFile = out_dict.get('config-file', None) + self.__deployFlag = out_dict.get('deploy', False) + self.__zone = out_dict.get("zone", None) + self.__hypervisorType = out_dict.get("hyp-type", None) + self.__tcPath = out_dict.get("tc-path",) + self.__requiredHw = out_dict.get("required-hardware") + if not all([self.__tcPath, self.__configFile]): + return FAILED + print "\n==== Parsing Input Options Successful ====" + return SUCCESS + return FAILED + + @VerifyAndExit("Marvin initialization failed, please check") + def start_marvin(self): + ''' + Initialize the Marvin + ''' + try: + obj_marvininit = MarvinInit(config_file=self.__configFile, + deploy_dc_flag=self.__deployFlag, + zone=self.__zone, + hypervisor_type=self.__hypervisorType, + user_logfolder_path=None) + if obj_marvininit and obj_marvininit.init() == SUCCESS: + self.__testClient = obj_marvininit.getTestClient() + self.__tcRunLogger = obj_marvininit.getLogger() + self.__parsedConfig = obj_marvininit.getParsedConfig() + self.__resultStream = obj_marvininit.getResultFile() + self.__logFolderPath = obj_marvininit.getLogFolderPath() + return SUCCESS + return FAILED + except Exception as e: + print "====Exception Occurred under start_marvin: %s ====" % \ + GetDetailExceptionInfo(e) + return FAILED + + def run_test_suites(self): + print "\n==== Started Running Test Cases ====" + xunit_out_path = "/tmp/marvin_xunit_out" + \ + str(random.randrange(1, 10000)) + ".xml" + marvin_tc_run_cmd = "nosetests-2.7 -s --with-marvin --marvin-config=%s --with-xunit --xunit-file=%s %s -a tags=advanced, required_hardware=%s --zone=%s --hypervisor=%s" + if os.path.isfile(self.__tcPath): + marvin_tc_run_cmd = marvin_tc_run_cmd % (self.__configFile, + xunit_out_path, self.__requiredHw, self.__zone, self.__hypervisorType) + if os.path.isdir(self.__tcPath): + marvin_tc_run_cmd = marvin_tc_run_cmd % (self.__configFile, + xunit_out_path, self.__requiredHw, self.__zone, self.__hypervisorType) + os.system(marvin_tc_run_cmd) + ''' + engine = TestCaseExecuteEngine(self.__testClient, + self.__parsedConfig, + tc_logger=self.__tcRunLogger) + if os.path.isfile(self.__tcPath): + engine.loadTestsFromFile(self.__tcPath) + elif os.path.isdir(self.__tcPath): + engine.loadTestsFromDir(self.__tcPath) engine.run() - n = n + 1 - - -def checkTCPath(tc_path): - ''' - Verifies if the tc_path is a folder or file and its existence - ''' - ret = {"tc_file": None, "tc_dir": None} - check = True - if tc_path is None: - printAndExit() - else: - if os.path.isfile(tc_path): - ret["tc_file"] = tc_path - elif os.path.isdir(tc_path): - ret["tc_dir"] = tc_path + ''' + print "\n==== Running Test Cases Successful ====" + + def do_deploy(self, args): + self.__deployFlag = True + self.parse_input_deploy(inputs=args) + self.start_marvin() + + def do_deploydc_and_runtest(self, args): + self.do_deploy(inputs=args) + self.parse_input_runtcs() + self.run_test_suites() + + def do_generateapis_from_apispecfile(self, args): + api_spec_file = "/etc/cloud/cli/commands.xml" + cs_api_folder = "." + if args: + inp = args.strip().split(' ') + for items in inp: + (key, value) = items.split('=') + if key.lower() == 'api-spec-file': + if os.path.exists(value): + api_spec_file = value + elif not os.path.exists(api_spec_file): + print "=== Mentioned api spec file :%s does not exists ===" % str(api_spec_file) + sys.exit(1) + if key.lower() == 'cs-folder-path': + cs_api_folder = self.create_marvin_api_folder(value) + cg = CodeGenerator(cs_api_folder) + if api_spec_file: + try: + cg.generateCodeFromXML(api_spec_file) + return + except Exception as e: + print "==== Generating apis from api spec file failed: %s ====" % str(e.message()) + sys.exit(1) + sys.exit(1) + + def create_marvin_api_folder(self, cs_folder_path='.'): + cs_api_folder = cs_folder_path + "/tools/marvin/marvin/cloudstackAPI" + if os.path.exists(cs_api_folder): + os.rmdir(cs_api_folder) else: - check = False - if check is False: - print"\nTC Path is Invalid.So Exiting" - exit(1) + os.makedirs(cs_api_folder) + return cs_api_folder - return ret + def do_generateapis_from_endpoint(self, args): + endpoint_url = 'http://%s:8096/client/api?command=listApis&\ +response=json' + cs_api_folder = "." + if args: + inp = args.strip().split(' ') + for items in inp: + (key, value) = items.split('=') + if key.lower() == 'endpoint': + cs_end_point = value + if key.lower() == 'cs-folder-path': + cs_api_folder = self.create_marvin_api_folder(value) + cg = CodeGenerator(cs_api_folder) + if cs_end_point: + try: + endpoint_url = endpoint_url % str(cs_end_point) + cg.generateCodeFromJSON(endpoint_url) + return + except Exception as e: + print "==== Generating apis from end point failed: %s ====" % str(e.message()) + sys.exit(1) + sys.exit(1) -if __name__ == "__main__": + def do_runtest(self, args): + self.parse_input_runtcs(args) + self.start_marvin() + self.run_test_suites() - ''' - 1. Parse and Check - ''' - out1 = parseAndCheck() - print "\nStep1 :Parsing Options And Check Went Fine" - - ''' - 2. Start Marvin - ''' - out2 = startMarvin(out1["cfg_file"], out1["load_flag"]) - print "\nStep2: Marvin Initialization Went Fine" - - ''' - 3. Check TC folder or Module and Path existence - ''' - out3 = checkTCPath(out1["tc_path"]) - print "\nStep3: TC Path Check Went Fine" - - ''' - 4. Run TCs - ''' - runTCs(out1["num_iter"], out3, out2) - print "\nStep4: TC Running Finished" + def install_marvin(self): + if self.__csFolder: + marvin_setup_file_path = self.__csFolder + "/tools/marvin/setup.py" + # step2: Build and install the Marvin + try: + os.system("python %s install" % str(marvin_setup_file_path)) + except Exception as e: + print "==== Marvin Installation Failed ====" + print "==== Marvin Installed Successfully ====" + + def do_build_and_install(self, args): + # step1: Generate the apis from spec file first + self.do_generateapis_from_apispecfile(args) + self.install_marvin() + + def do_sync_and_install(self, args): + # step1: Generate the apis from spec file first + self.do_generateapis_from_endpoint(args) + self.install_marvin() + + +class MarvinCliParser(OptionParser): + + def format_help(self, formatter=None): + if formatter is None: + formatter = self.formatter + print MarvinCliHelp.print_msg("Usage: marvincli [cmd] [options].See, the below cmds for more information \n\n") + print MarvinCliHelp.do_printhelp() + return "\n===========================================================================\n" + + +def main(): + parser = MarvinCliParser() + parser.add_option("-v", "--version", + action="store_true", dest="version", default=False, + help="prints marvin cli version information") + (options, args) = parser.parse_args() + if options.version: + MarvinCliHelp.help_printversion() + sys.exit(0) + if len(sys.argv) > 1: + if sys.argv[1].lower() in ["deploydc", "deploydc_and_runtest", "generateapis_from_endpoint", + "generateapis_from_apispecfile", "runtest", "sync_and_install", "build_and_install"]: + MarvinCli().onecmd(' '.join(args)) + else: + print "\n==== Invalid Command ====" + sys.exit(1) + sys.exit(0) + +if __name__ == "__main__": + main() http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6bd5041f/tools/marvin/marvin/marvinInit.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/marvinInit.py b/tools/marvin/marvin/marvinInit.py index 56ea509..e888484 100644 --- a/tools/marvin/marvin/marvinInit.py +++ b/tools/marvin/marvin/marvinInit.py @@ -40,7 +40,7 @@ import os class MarvinInit: def __init__(self, config_file, - deploy_dc_flag=None, + deploy_dc_flag=False, test_mod_name="deploydc", zone=None, hypervisor_type=None, @@ -109,12 +109,19 @@ class MarvinInit: ''' try: if not self.__hypervisorType: - self.__hypervisorType = XEN_SERVER + if self.__parsedConfig and self.__parsedConfig.zones is not None: + for zone in self.__parsedConfig.zones: + for pod in zone.pods and pod is not None: + for cluster in pod.clusters and cluster is not None: + self.__hypervisorType = cluster.hypervisor + break if not self.__zoneForTests: if self.__parsedConfig and self.__parsedConfig.zones is not None: for zone in self.__parsedConfig.zones: self.__zoneForTests = zone.name break + if not self.__hypervisorType: + self.__hypervisorType = XEN_SERVER return SUCCESS except Exception as e: print "\n Exception Occurred Under init " \ @@ -133,13 +140,16 @@ class MarvinInit: @Output : SUCCESS or FAILED ''' try: + print "\n==== Marvin Init Started ====" if ((self.__parseConfig() != FAILED) and (self.__setHypervisorAndZoneInfo())and (self.__setTestDataPath() != FAILED) and (self.__initLogging() != FAILED) and (self.__createTestClient() != FAILED) and (self.__deployDC() != FAILED)): + print "\n==== Marvin Init Successful ====" return SUCCESS + print "\n==== Marvin Init Failed ====" return FAILED except Exception as e: print "\n Exception Occurred Under init " \ @@ -232,10 +242,8 @@ class MarvinInit: self.__parsedConfig, self.__tcRunLogger) ret = deploy_obj.deploy() - if ret == SUCCESS: - print "Deploy DC Successful" - else: - print "Deploy DC Failed" + if ret != SUCCESS: + print "==== Deploy DC Failed ====" return ret except Exception as e: print "\n Exception Occurred Under __deployDC : %s" % \ http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6bd5041f/tools/marvin/marvin/tcExecuteEngine.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/tcExecuteEngine.py b/tools/marvin/marvin/tcExecuteEngine.py index e2f4d11..11b5d09 100644 --- a/tools/marvin/marvin/tcExecuteEngine.py +++ b/tools/marvin/marvin/tcExecuteEngine.py @@ -24,50 +24,53 @@ from functools import partial class TestCaseExecuteEngine(object): - def __init__(self, testclient, config, tc_logger=None, debug_stream=None): + def __init__(self, testclient, config, tc_logger=None, + debug_stream=sys.stdout): """ - Initialize the testcase execution engine, just the basics here - @var testcaseLogFile: client log file - @var testResultLogFile: summary report file + Initialize the testcase execution engine """ - self.testclient = testclient - self.config = config - self.tcRunLogger = tc_logger - self.debugStream = debug_stream - self.loader = unittest.loader.TestLoader() - self.suite = None + self.__testClient = testclient + self.__parsedConfig = config + self.__tcRunLogger = tc_logger + self.__debugStream = debug_stream + self.__loader = unittest.loader.TestLoader() + self.__suite = None - def loadTestsFromDir(self, testDirectory): + def loadTestsFromDir(self, test_directory): """ Load the test suites from a package with multiple test files """ - self.suite = self.loader.discover(testDirectory) - self.injectTestCase(self.suite) + self.__suite = self.__loader.discover(test_directory) + self.injectTestCase(self.__suite) def loadTestsFromFile(self, file_name): """ Load the tests from a single script/module """ if os.path.isfile(file_name): - self.suite = self.loader.discover(os.path.dirname(file_name), - os.path.basename(file_name)) - self.injectTestCase(self.suite) + self.__suite = self.__loader.discover(os.path.dirname(file_name), + os.path.basename(file_name)) + self.injectTestCase(self.__suite) - def injectTestCase(self, testSuites): - for test in testSuites: + def injectTestCase(self, test_suites): + for test in test_suites: if isinstance(test, unittest.BaseTestSuite): self.injectTestCase(test) else: # inject testclient and logger into each unittest - self.tcRunLogger.name = test.__str__() - setattr(test, "testClient", self.testclient) - setattr(test, "config", self.config) - setattr(test, "debug", self.tcRunLogger.debug) - setattr(test.__class__, "clstestclient", self.testclient) + setattr(test, "debug", self.__tcRunLogger.debug) + setattr(test, "info", self.__tcRunLogger.info) + setattr(test, "warn", self.__tcRunLogger.warning) + setattr(test, "error", self.__tcRunLogger.error) + setattr(test, "clstestclient", self.__testClient) + setattr(test, "testClient", self.__testClient) + setattr(test, "config", self.__parsedConfig) if hasattr(test, "user"): - # attribute when test is entirely executed as user - self.testclient.\ - getUserApiClient(test.UserName, - test.DomainName, - test.AcctType) + # when the class-level attr applied. all test runs as + # 'user' + self.__testClient.getUserApiClient(test.UserName, + test.DomainName, + test.AcctType) def run(self): - if self.suite: - unittest.TextTestRunner(stream=self.debugStream, - verbosity=2).run(self.suite) + if self.__suite: + print "\n==== Test Suite :%s Started ====" % (str(self.__suite)) + unittest.TextTestRunner(stream=self.__debugStream, + verbosity=2).run(self.__suite) + print "\n==== Test Suite :%s Finished ====" % (str(self.__suite)) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/6bd5041f/tools/marvin/setup.py ---------------------------------------------------------------------- diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index 03e53c9..2a44a16 100644 --- a/tools/marvin/setup.py +++ b/tools/marvin/setup.py @@ -55,6 +55,7 @@ setup(name="Marvin", py_modules=['marvin.marvinPlugin'], zip_safe=False, entry_points={ - 'nose.plugins': ['marvinPlugin = marvin.marvinPlugin:MarvinPlugin'] + 'nose.plugins': ['marvinPlugin = marvin.marvinPlugin:MarvinPlugin'], + 'console_scripts': ['marvincli = marvin.deployAndRun:main'] }, )