Eutester 0.0.6 documentation

eutester.eutestcase

Contents

Source code for eutester.eutestcase

import unittest
import inspect
import time
import gc
import argparse
import re
import sys
import os
import types
import traceback
from eutester.eulogger import Eulogger
from eutester.euconfig import EuConfig
import StringIO
import copy

'''
This is the base class for any test case to be included in the Eutester repo. It should include any
functionality that we expected to be repeated in most of the test cases that will be written.

Currently included:
 - Debug method
 - Allow parameterized test cases
 - Method to run test case
 - Run a list of test cases
 - Start, end and current status messages
 - Enum class for possible test results
 
Necessary to work on:
 - Argument parsing
 - Metric tracking (need to define what metrics we want
 - Standardized result summary
 - Logging standardization
 - Use docstring as description for test case
 - Standardized setUp and tearDown that provides useful/necessary cloud resources (ie group, keypair, image)
'''


class EutesterTestResult():
[docs] ''' standardized test results ''' not_run="not_run" passed="passed" failed="failed" class TestColor():
[docs] reset = '\033[0m' #formats formats={'reset':'0', 'bold':'1', 'dim':'2', 'uline':'4', 'blink':'5', 'reverse':'7', 'hidden':'8', } foregrounds = {'black':30, 'red':31, 'green':32, 'yellow':33, 'blue':34, 'magenta':35, 'cyan':36, 'white':37, 'setasdefault':39} backgrounds = {'black':40, 'red':41, 'green':42, 'yellow':43, 'blue':44, 'magenta':45, 'cyan':46, 'white':47, 'setasdefault':49} #list of canned color schemes, for now add em as you need 'em? canned_colors ={'reset' : '\033[0m', #self.TestColor.get_color(fg=0) 'whiteonblue' : '\33[1;37;44m', #get_color(fmt=bold, fg=37,bg=44) 'whiteongreen' : '\33[1;37;42m', 'red' : '\33[31m', #TestColor.get_color(fg=31) 'failred' : '\033[101m', #TestColor.get_color(fg=101) 'blueongrey' : '\33[1;34;47m', #TestColor.get_color(fmt=bold, fg=34, bg=47)#'\33[1;34;47m' 'redongrey' : '\33[1;31;47m', #TestColor.get_color(fmt=bold, fg=31, bg=47)#'\33[1;31;47m' 'blinkwhiteonred' : '\33[1;5;37;41m', #TestColor.get_color(fmt=[bold,blink],fg=37,bg=41)# } @classmethod def get_color(cls,fmt=0,fg='', bg=''):
[docs] ''' Description: Method to return ascii color codes to format terminal output. Examples: blinking_red_on_black = get_color('blink', 'red', 'blue') bold_white_fg = get_color('bold', 'white, '') green_fg = get_color('','green','') print bold_white_fg+"This text is bold white"+TestColor.reset :type fmt: color attribute :param fmt: An integer or string that represents an ascii color attribute. see TestColor.formats :type fg: ascii foreground attribute :param fg: An integer or string that represents an ascii foreground color attribute. see TestColor.foregrounds :type bg: ascii background attribute :param bg: An integer or string that represents an ascii background color attribute. see TestColor.backgrounds ''' fmts='' if not isinstance(fmt, types.ListType): fmt = [fmt] for f in fmt: if isinstance(f,types.StringType): f = TestColor.get_format_from_string(f) if f: fmts += str(f)+';' if bg: if isinstance(bg,types.StringType): bg = TestColor.get_bg_from_string(bg) if bg: bg = str(bg) if fg: if isinstance(fg,types.StringType): fg = TestColor.get_fg_from_string(fg) if fg: fg = str(fg)+';' return '\033['+str(fmts)+str(fg)+str(bg)+'m' @classmethod
def get_format_from_string(cls,format):
[docs] if format in TestColor.formats: return TestColor.formats[format] else: return '' @classmethod
def get_fg_from_string(cls,fg):
[docs] if fg in TestColor.foregrounds: return TestColor.foregrounds[fg] else: return '' @classmethod
def get_bg_from_string(cls,bg):
[docs] if bg in TestColor.backgrounds: return TestColor.backgrounds[bg] else: return '' @classmethod
def get_canned_color(cls,color):
[docs] try: return TestColor.canned_colors[color] except: return "" class EutesterTestUnit():
[docs] ''' Description: Convenience class to run wrap individual methods, and run and store and access results. type method: method param method: The underlying method for this object to wrap, run and provide information on type args: list of arguments param args: the arguments to be fed to the given 'method' type eof: boolean param eof: boolean to indicate whether a failure while running the given 'method' should end the test case exectution. ''' def __init__(self,method, *args, **kwargs): self.method = method self.method_possible_args = EutesterTestCase.get_meth_arg_names(self.method) self.args = args self.kwargs = kwargs self.name = str(method.__name__) self.result=EutesterTestResult.not_run self.time_to_run=0 self.description=self.get_test_method_description() self.eof=False self.error = "" print "Creating testunit:"+str(self.name)+", args:" for count, thing in enumerate(args): print '{0}. {1}'.format(count, thing) for name, value in kwargs.items(): print '{0} = {1}'.format(name, value) @classmethod def create_testcase_from_method(cls, method, eof=False, *args, **kwargs):
[docs] ''' Description: Creates a EutesterTestUnit object from a method and set of arguments to be fed to that method type method: method param method: The underlying method for this object to wrap, run and provide information on type args: list of arguments param args: the arguments to be fed to the given 'method' ''' testunit = EutesterTestUnit(method, args, kwargs) testunit.eof = eof return testunit def set_kwarg(self,kwarg,val):
[docs] self.kwargs[kwarg]=val def get_test_method_description(self):
[docs] ''' Description: Attempts to derive test unit description for the registered test method. Keys off the string "Description:" preceded by any amount of white space and ending with either a blank line or the string "EndDescription". This is used in debug output when providing info to the user as to the method being run as a testunit's intention/description. ''' desc = "\nMETHOD:"+str(self.name)+", TEST DESCRIPTION:\n" ret = [] add = False try: doc = str(self.method.__doc__) if not doc or not re.search('Description:',doc): try: desc = desc+"\n".join(self.method.im_func.func_doc.title().splitlines()) except:pass return desc has_end_marker = re.search('EndDescription', doc) for line in doc.splitlines(): line = line.lstrip().rstrip() if re.search('^Description:',line.lstrip()): add = True if not has_end_marker: if not re.search('\w',line): if add: break add = False else: if re.search('^EndDescription'): add = False break if add: ret.append(line) except Exception, e: print('get_test_method_description: error'+str(e)) if ret: desc = desc+"\n".join(ret) return desc def run(self):
[docs] ''' Description: Wrapper which attempts to run self.method and handle failures, record time. ''' for count, thing in enumerate(self.args): print 'ARG:{0}. {1}'.format(count, thing) for name, value in self.kwargs.items(): print 'KWARG:{0} = {1}'.format(name, value) try: start = time.time() if not self.args and not self.kwargs: ret = self.method() else: ret = self.method(*self.args, **self.kwargs) self.result=EutesterTestResult.passed return ret except Exception, e: out = StringIO.StringIO() traceback.print_exception(*sys.exc_info(),file=out) out.seek(0) buf = out.read() print TestColor.get_canned_color('failred')+buf+TestColor.reset self.error = str(e) self.result = EutesterTestResult.failed if self.eof: raise e else: pass finally: self.time_to_run = int(time.time()-start) class EutesterTestCase(unittest.TestCase):
[docs] color = TestColor() def __init__(self,name=None, debugmethod=None, use_default_file=True, default_config='eutester.conf'): return self.setuptestcase(name=name, debugmethod=debugmethod, use_default_file=use_default_file, default_config=default_config) def setuptestcase(self, name=None, debugmethod=None, use_default_file=True, default_config='eutester.conf' ):
[docs] self.name = self._testMethodName = name if not self.name: callerfilename=inspect.getouterframes(inspect.currentframe())[1][1] self.name = os.path.splitext(os.path.basename(callerfilename))[0] self._testMethodName = self.name print "setuptestname:"+str(name) self.debugmethod = debugmethod if not self.debugmethod: self.setup_debugmethod() if not hasattr(self,'testlist'): self.testlist = [] self.list = None if not hasattr(self,'configfiles'): self.configfiles=[] self.default_config = default_config self.use_default_file = use_default_file if use_default_file: #first add $USERHOME/.eutester/eutester.conf if it exists self.default_config=self.get_default_userhome_config(fname=default_config) if self.default_config: self.configfiles.append(self.default_config) if not hasattr(self,'args'): self.args=argparse.Namespace() self.show_self() def compile_all_args(self):
[docs] self.setup_parser() self.get_args() def setup_parser(self,
[docs] testname=None, description=None, emi=True, zone=True, vmtype=True, keypair=True, credpath=True, password=True, config=True, configblocks=True, ignoreblocks=True, color=True, testlist=True, userdata=True, instance_user=True, instance_password=True, region=True): ''' Description: Convenience method to setup argparse parser and some canned default arguments, based upon the boolean values provided. For each item marked as 'True' this method will add pre-defined command line arguments, help strings and default values. This will then be available by the end script as an alternative to recreating these items on a per script bassis. :type testname: string :param testname: Name used for argparse (help menu, etc.) :type description: string :param description: Description used for argparse (help menu, etc.) :type emi: boolean :param emi: Flag to present the emi command line argument/option for providing an image emi id via the cli :type zone: boolean :param zone: Flag to present the zone command line argument/option for providing a zone via the cli :type vmtype: boolean :param vmtype: Flag to present the vmtype command line argument/option for providing a vmtype via the cli :type keypair: boolean :param kepair: Flag to present the keypair command line argument/option for providing a keypair via the cli :type credpath: boolean :param credpath: Flag to present the credpath command line argument/option for providing a local path to creds via the cli :type password: boolean :param password: Flag to present the password command line argument/option for providing password used in establishing machine ssh sessions :type config: boolean :param config: Flag to present the config file command line argument/option for providing path to config file :type configblocks: string list :param configblocks: Flag to present the configblocks command line arg/option used to provide list of configuration blocks to read from Note: By default if a config file is provided the script will only look for blocks; 'globals', and the filename of the script being run. :type ignoreblocks: string list :param ignoreblocks: Flag to present the configblocks command line arg/option used to provide list of configuration blocks to ignore if present in configfile Note: By default if a config file is provided the script will look for blocks; 'globals', and the filename of the script being run :type testlist: string list :param testlist: Flag to present the testlist command line argument/option for providing a list of testnames to run :type userdata: boolean :param userdata: Flag to present the userdata command line argument/option for providing userdata to instance(s) within test :type instance_user: boolean :param instance_user: Flag to present the instance_user command line argument/option for providing an ssh username for instance login via the cli :type instance_password: boolean :param instance_password: Flag to present the instance_password command line argument/option for providing a ssh password for instance login via the cli :type use_color: flag :param use_color: Flag to enable/disable use of ascci color codes in debug output. ''' testname = testname or self.name description = description or "Test Case Default Option Parser Description" #create parser parser = argparse.ArgumentParser( prog=testname, description=description) #add some typical defaults: if emi: parser.add_argument('--emi', help="pre-installed emi id which to execute these tests against", default=None) if credpath: parser.add_argument('--credpath', help="path to credentials", default=None) if password: parser.add_argument('--password', help="password to use for machine root ssh access", default=None) if config: parser.add_argument('--config', help='path to config file', default=None) if configblocks: parser.add_argument('--configblocks', nargs='+', help="Config sections/blocks in config file to read in", default=[]) if ignoreblocks: parser.add_argument('--ignoreblocks', nargs='+', help="Config blocks to ignore, ie:'globals', 'my_scripts_name', etc..", default=[]) if testlist: parser.add_argument('--tests', nargs='+', help="test cases to be executed", default = []) if keypair: parser.add_argument('--keypair', help="Keypair to use in this test", default=None) if zone: parser.add_argument('--zone', help="Zone to use in this test", default=None) if vmtype: parser.add_argument('--vmtype', help="Virtual Machine Type to use in this test", default='c1.medium') if userdata: parser.add_argument('--userdata', help="User data string to provide instance run within this test", default=None) if instance_user: parser.add_argument('--instance_user', help="Username used for ssh login. Default:'root'", default='root') if instance_password: parser.add_argument('--instance_passsword', help="Password used for ssh login. When value is 'None' ssh keypair will be used and not username/password, default:'None'", default=None) if region: parser.add_argument('--region', help="Use AWS region instead of Eucalyptus", default=None) if color: parser.add_argument('--use_color', dest='use_color', action='store_true', default=False) self.parser = parser return parser def disable_color(self):
[docs] self.set_arg('use_color', False) self.use_color = False def enable_color(self):
[docs] self.set_arg('use_color', True) self.use_color = True def setup_debugmethod(self, testcasename=None):
[docs] name = testcasename print "Starting setup_debugmethod"+str(name) if not name: if hasattr(self,'name'): if isinstance(self.name, types.StringType): name = self.name else: name = 'EutesterTestCase' logger = Eulogger(identifier=str(name)) self.debugmethod = logger.log.debug def debug(self,msg,traceback=1,color=None, linebyline=True):
[docs] ''' Description: Method for printing debug type msg: string param msg: Mandatory string buffer to be printed in debug message type traceback: integer param traceback: integer value for what frame to inspect to derive the originating method and method line number type color: TestColor color param color: Optional ascii text color scheme. See TestColor for more info. ''' try: if not self.debugmethod: self.setup_debugmethod() except: self.setup_debugmethod() if self.has_arg("use_color"): self.use_color = bool(self.args.use_color) else: self.use_color = False colorprefix="" colorreset="" #if a color was provide if color and self.use_color: colorprefix = TestColor.get_canned_color(color) or color colorreset = str(TestColor.get_canned_color('reset')) msg = str(msg) curframe = None curframe = inspect.currentframe(traceback) lineno = curframe.f_lineno self.curframe = curframe frame_code = curframe.f_code frame_globals = curframe.f_globals functype = type(lambda: 0) funcs = [] for func in gc.get_referrers(frame_code): if type(func) is functype: if getattr(func, "func_code", None) is frame_code: if getattr(func, "func_globals", None) is frame_globals: funcs.append(func) if len(funcs) > 1: return None cur_method= funcs[0].func_name if funcs else "" if linebyline: for line in msg.split("\n"): self.debugmethod("("+str(cur_method)+":"+str(lineno)+"): "+colorprefix+line.strip()+colorreset ) else: self.debugmethod("("+str(cur_method)+":"+str(lineno)+"): "+colorprefix+str(msg)+colorreset ) def run_test_list_by_name(self, list):
[docs] unit_list = [] for test in list: unit_list.append( self.create_testunit_by_name(test) ) ### Run the EutesterUnitTest objects return self.run_test_case_list(unit_list) def create_testunit_from_method(self,method, *args, **kwargs):
[docs] ''' Description: Convenience method calling EutesterTestUnit. Creates a EutesterTestUnit object from a method and set of arguments to be fed to that method :type method: method :param method: The underlying method for this object to wrap, run and provide information on :type eof: boolean :param eof: Boolean to indicate whether this testunit should cause a test list to end of failure :type autoarg: boolean :param autoarg: Boolean to indicate whether to autopopulate this testunit with values from global testcase.args :type args: list of positional arguments :param args: the positional arguments to be fed to the given testunit 'method' :type kwargs: list of keyword arguements :param kwargs: list of keyword :rtype: EutesterTestUnit :returns: EutesterTestUnit object ''' eof=False autoarg=True methvars = self.get_meth_arg_names(method) #Pull out value relative to this method, leave in any that are intended to be passed through if 'autoarg' in kwargs: if 'autoarg' in methvars: autoarg = kwargs['autoarg'] else: autoarg = kwargs.pop('autoarg') if 'eof' in kwargs: if 'eof' in methvars: eof = kwargs['eof'] else: eof = kwargs.pop('eof') testunit = EutesterTestUnit(method, *args, **kwargs) testunit.eof = eof #if autoarg, auto populate testunit arguements from local testcase.args namespace values if autoarg: self.populate_testunit_with_args(testunit) return testunit def status(self,msg,traceback=2, b=1,a=0 ,testcolor=None):
[docs] ''' Description: Convenience method to format debug output :type msg: string :param msg: The string to be formated and printed via self.debug :type traceback: integer :param traceback: integer value for what frame to inspect to derive the originating method and method line number :type b: integer :param b:number of blank lines to print before msg :type a: integer :param a:number of blank lines to print after msg :type testcolor: TestColor color :param testcolor: Optional TestColor ascii color scheme ''' alines = "" blines = "" for x in xrange(0,b): blines=blines+"\n" for x in xrange(0,a): alines=alines+"\n" line = "-------------------------------------------------------------------------" out = blines+line+"\n"+msg+"\n"+line+alines self.debug(out, traceback=traceback, color=testcolor,linebyline=False) def startmsg(self,msg=""):
[docs] msg = "- STARTING TESTUNIT: - " + msg self.status(msg, traceback=3,testcolor=TestColor.get_canned_color('whiteonblue')) def endsuccess(self,msg=""):
[docs] msg = "- UNIT ENDED - " + msg self.status(msg, traceback=2,a=1, testcolor=TestColor.get_canned_color('whiteongreen')) def endfailure(self,msg="" ):
[docs] msg = "- FAILED - " + msg self.status(msg, traceback=2,a=1,testcolor=TestColor.get_canned_color('failred')) def resultdefault(self,msg,printout=True,color='blueongrey'):
[docs] if printout: self.debug(msg,traceback=2,color=TestColor.get_canned_color('blueongrey'),linebyline=False) msg = self.format_line_for_color(msg, color) return msg def resultfail(self,msg,printout=True, color='redongrey'):
[docs] if printout: self.debug(msg,traceback=2, color=TestColor.get_canned_color('redongrey'),linebyline=False) msg = self.format_line_for_color(msg, color) return msg def resulterr(self,msg,printout=True,color='failred'):
[docs] if printout: self.debug(msg,traceback=2, color=TestColor.get_canned_color(color),linebyline=False) msg = self.format_line_for_color(msg, color) return msg def format_line_for_color(self,msg,color):
[docs] if not self.use_color: return msg end="" if msg.endswith('\n'): msg = msg.rstrip() end="\n" msg = TestColor.get_canned_color(color)+str(msg)+TestColor.reset+end return msg def get_pretty_args(self,testunit):
[docs] ''' Description: Returns a string buf containing formated arg:value for printing later :type: testunit: Eutestcase.eutestertestunit object :param: testunit: A testunit object for which the namespace args will be used :rtype: string :returns: formated string containing args and their values. ''' buf = "\nEnd on Failure:" +str(testunit.eof) buf += "\nPassing ARGS:" if not testunit.args and not testunit.kwargs: buf += '\"\"\n' else: buf += "\n---------------------\n" varnames = self.get_meth_arg_names(testunit.method) if testunit.args: for count,arg in enumerate(testunit.args): buf += str(varnames[count+1])+" : "+str(arg)+"\n" if testunit.kwargs: for key in testunit.kwargs: buf += str(key)+" : "+str(testunit.kwargs[key])+"\n" buf += "---------------------\n" return buf def run_test_case_list(self, list, eof=True, clean_on_exit=True, printresults=True):
[docs] ''' Desscription: wrapper to execute a list of ebsTestCase objects :type list: list :param list: list of EutesterTestUnit objects to be run :type eof: boolean :param eof: Flag to indicate whether run_test_case_list should exit on any failures. If this is set to False it will exit only when a given EutesterTestUnit fails and has it's eof flag set to True. :type clean_on_exit: boolean :param clean_on_exit: Flag to indicate if clean_on_exit should be ran at end of test list execution. :type printresults: boolean :param printresults: Flag to indicate whether or not to print a summary of results upon run_test_case_list completion. :rtype: integer :returns: integer exit code to represent pass/fail of the list executed. ''' self.testlist = list start = time.time() tests_ran=0 test_count = len(list) try: for test in list: tests_ran += 1 startbuf = "" argbuf =self.get_pretty_args(test) startbuf += str(test.description)+str(argbuf) startbuf += 'Running list method: "'+str(self.print_testunit_method_arg_values(test))+'"' self.startmsg(startbuf) try: test.run() except Exception, e: self.debug('Testcase:'+ str(test.name)+' error:'+str(e)) if eof or (not eof and test.eof): self.endfailure(str(test.name)) raise e else: self.endfailure(str(test.name)) else: self.endsuccess(str(test.name)) self.debug(self.print_test_list_short_stats(list)) finally: elapsed = int(time.time()-start) msgout = "RUN TEST CASE LIST DONE:\n" msgout += "Ran "+str(tests_ran)+"/"+str(test_count)+" tests in "+str(elapsed)+" seconds\n" if printresults: try: self.debug("Printing pre-cleanup results:") msgout += self.print_test_list_results(list=list,printout=False) self.status(msgout) except:pass try: if clean_on_exit: cleanunit = self.create_testunit_from_method(self.clean_method) list.append(cleanunit) try: cleanunit.run() except: pass if printresults: msgout = self.print_test_list_results(list=list,printout=False) self.status(msgout) except: pass self.testlist = copy.copy(list) total = 0 passed = 0 for test in list: total += 1 if test.result == EutesterTestResult.passed: passed += 1 print "passed:"+str(passed)+" out of total:"+str(total) if total != passed: return(1) else: return(0) def has_arg(self,arg):
[docs] ''' Description: If arg is present in local testcase args namespace, will return True, else False :type arg: string :param arg: string name of arg to check for. :rtype: boolean :returns: True if arg is present, false if not ''' arg = str(arg) if hasattr(self,'args'): if self.args and (arg in self.args): return True return False def get_arg(self,arg):
[docs] ''' Description: Fetchs the value of an arg within the local testcase args namespace. If the arg does not exist, None will be returned. :type arg: string :param arg: string name of arg to get. :rtype: value :returns: Value of arguement given, or None if not found ''' if self.has_arg(arg): return getattr(self.args,str(arg)) return None def add_arg(self,arg,value):
[docs] ''' Description: Adds an arg 'arg' within the local testcase args namespace and assigns it 'value'. If arg exists already in testcase.args, then an exception will be raised. :type arg: string :param arg: string name of arg to set. :type value: value :param value: value to set arg to ''' if self.has_arg(arg): raise Exception("Arg"+str(arg)+'already exists in args') else: self.args.__setattr__(arg,value) def set_arg(self,arg, value):
[docs] ''' Description: Sets an arg 'arg' within the local testcase args namespace to 'value'. If arg does not exist in testcase.args, then it will be created. :type arg: string :param arg: string name of arg to set. :type value: value :param value: value to set arg to ''' if self.has_arg(arg): new = argparse.Namespace() for val in self.args._get_kwargs(): if arg != val[0]: new.__setattr__(val[0],val[1]) new.__setattr__(arg,value) self.args = new else: self.args.__setattr__(arg,value) def clean_method(self):
[docs] raise Exception("Clean_method needs not implemented. Was run_list using clean_on_exit?") def print_test_list_results(self,list=None, printout=True, printmethod=None):
[docs] ''' Description: Prints a formated list of results for a list of EutesterTestUnits :type list: list :param list: list of EutesterTestUnits :type printout: boolean :param printout: boolean to flag whether to print using printmethod or self.debug, or to return a string buffer representing the results output :type printmethod: method :param printmethod: method to use for printing test result output. Default is self.debug ''' buf = "TESTUNIT LIST SUMMARY FOR '"+str(self.name)+"'\n" if list is None: list=self.testlist if not list: raise Exception("print_test_list_results, error: No Test list provided") if printmethod is None: printmethod = lambda msg: self.debug(msg,linebyline=False) printmethod("Test list results for testcase:"+str(self.name)) for testunit in list: buf += self.resultdefault("\n"+ self.getline(80)+"\n", printout=False) pmethod = self.resultfail if not testunit.result == EutesterTestResult.passed else self.resultdefault buf += pmethod(str("TEST: "+str(testunit.name)).ljust(50)+str(" RESULT:"+testunit.result).ljust(10)+str(' Time:'+str(testunit.time_to_run)).ljust(0),printout=False) buf += pmethod("\nVALUES: "+str(self.print_testunit_method_arg_values(testunit)), printout=False) if testunit.result == EutesterTestResult.failed: buf += "\n"+str(self.resulterr('ERROR('+str(testunit.name)+'): '+str(testunit.error), printout=False)) buf += self.resultdefault("\n"+ self.getline(80)+"\n", printout=False) buf += str(self.print_test_list_short_stats(list)) if printout: printmethod(buf) else: return buf def print_test_list_short_stats(self,list,printmethod=None):
[docs] results={} mainbuf = "RESULTS SUMMARY FOR '"+str(self.name)+"':\n" fieldsbuf = "" resultsbuf= "" total = 0 elapsed = 0 #initialize a dict containing all the possible defined test results fields = dir(EutesterTestResult) for fieldname in fields[2:len(fields)]: results[fieldname]=0 #increment values in results dict based upon result of each testunit in list for testunit in list: total += 1 elapsed += testunit.time_to_run results[testunit.result] += 1 fieldsbuf += str('| TOTAL').ljust(10) resultsbuf += str('| ' + str(total)).ljust(10) for field in results: fieldsbuf += str('| ' + field.upper()).ljust(10) resultsbuf += str('| ' + str(results[field])).ljust(10) fieldsbuf += str('| TIME_ELAPSED').ljust(10) resultsbuf += str('| '+str(elapsed)).ljust(10) mainbuf += "\n"+self.getline(len(fieldsbuf))+"\n" mainbuf += fieldsbuf mainbuf += "\n"+self.getline(len(fieldsbuf))+"\n" mainbuf += resultsbuf mainbuf += "\n"+self.getline(len(fieldsbuf))+"\n" if printmethod: printmethod(mainbuf) return mainbuf @classmethod
def get_testunit_method_arg_dict(cls,testunit):
[docs] argdict={} spec = inspect.getargspec(testunit.method) if isinstance(testunit.method,types.FunctionType): argnames = spec.args else: argnames = spec.args[1:len(spec.args)] defaults = spec.defaults or [] #Initialize the return dict for argname in argnames: argdict[argname]='<!None!>' #Set the default values of the testunits method for x in xrange(0,len(defaults)): argdict[argnames.pop()]=defaults[len(defaults)-x-1] #Then overwrite those with the testunits kwargs values for kwarg in testunit.kwargs: argdict[kwarg]=testunit.kwargs[kwarg] #then add the positional args in if they apply... for count, value in enumerate(testunit.args): argdict[argnames[count]]=value return argdict @classmethod
def print_testunit_method_arg_values(cls,testunit):
[docs] buf = testunit.name+"(" argdict = EutesterTestCase.get_testunit_method_arg_dict(testunit) for arg in argdict: buf += str(arg)+":"+str(argdict[arg])+"," buf = buf.rstrip(',') buf += ")" return buf def getline(self,len):
[docs] buf = '' for x in xrange(0,len): buf += '-' return buf def run_method_by_name(self,name, obj=None, *args, **kwargs):
[docs] ''' Description: Find a method within an instance of obj and run that method with either args/kwargs provided or any self.args which match the methods varname. :type name: string :param name: Name of method to look for within instance of object 'obj' :type obj: class instance :param obj: Instance type, defaults to self testcase object :type args: positional arguements :param args: None or more positional arguments to be passed to method to be run :type kwargs: keyword arguments :param kwargs: None or more keyword arguements to be passed to method to be run ''' obj = obj or self meth = getattr(obj,name) return self.do_with_args(meth, *args, **kwargs) def create_testunit_by_name(self, name, obj=None, eof=True, autoarg=True, *args,**kwargs ):
[docs] ''' Description: Attempts to match a method name contained with object 'obj', and create a EutesterTestUnit object from that method and the provided positional as well as keyword arguments provided. :type name: string :param name: Name of method to look for within instance of object 'obj' :type obj: class instance :param obj: Instance type, defaults to self testcase object :type args: positional arguements :param args: None or more positional arguments to be passed to method to be run :type kwargs: keyword arguments :param kwargs: None or more keyword arguements to be passed to method to be run ''' eof=False autoarg=True #Pull out value relative to this method, leave in any that are intended to be passed through if 'autoarg' in kwargs: if 'autoarg' in methvars: autoarg = kwargs['autoarg'] else: autoarg = kwargs.pop('autoarg') if 'eof' in kwargs: if 'eof' in methvars: eof = kwargs['eof'] else: eof = kwargs.pop('eof') if 'obj' in kwargs: if 'obj' in methvars: obj = kwargs['obj'] else: obj = kwargs.pop('obj') obj = obj or self meth = getattr(obj,name) methvars = self.get_meth_arg_names(meth) testunit = EutesterTestUnit(meth, *args, **kwargs) testunit.eof = eof #if autoarg, auto populate testunit arguements from local testcase.args namespace values if autoarg: self.populate_testunit_with_args(testunit) return testunit def get_args(self,use_cli=True, file_sections=[]):
[docs] ''' Description: Method will attempt to retrieve all command line arguments presented through local testcase's 'argparse' methods, as well as retrieve all EuConfig file arguments. All arguments will be combined into a single namespace object held locally at 'testcase.args'. Note: cli arg 'config' must be provided for config file valus to be store in self.args. :type use_cli: boolean :param use_cli: Boolean to indicate whether or not to create and read from a cli argparsing object :type use_default_file: boolean :param use_default_files: Boolean to indicate whether or not to read default config file at $HOME/.eutester/eutester.conf (not indicated by cli) :type sections: list :param sections: list of EuConfig sections to read configuration values from, and store in self.args. :rtype: arparse.namespace obj :returns: namespace object with values from cli and config file arguements ''' configfiles=[] args=None #build out a namespace object from the config file first cf = argparse.Namespace() if self.use_default_file and self.default_config: try: configfiles.append(self.default_config) except Exception, e: self.debug("Unable to read config from file: " + str(e)) #Setup/define the config file block/sections we intend to read from confblocks = file_sections or ['MEMO','globals'] if self.name: confblocks.append(self.name) if use_cli: #See if we have CLI args to read if not hasattr(self,'parser') or not self.parser: self.setup_parser() #first get command line args to see if there's a config file cliargs = self.parser.parse_args() #if a config file was passed, combine the config file and command line args into a single namespace object if cliargs: #Check to see if there's explicit config sections to read if 'configblocks' in cliargs.__dict__: confblocks = confblocks +cliargs.configblocks #Check to see if there's explicit config sections to ignore if 'ignoreblocks' in cliargs.__dict__: for block in cliargs.ignoreblocks: if block in confblocks: confblocks.remove(block) #if a file or list of config files is specified add it to our list... if ('config' in cliargs.__dict__) and cliargs.config: for cfile in str(cliargs.config).split(','): if not cfile in configfiles: configfiles.append(cfile) #store config block list for debug purposes cf.__setattr__('configsections',copy.copy(confblocks)) #create euconfig configparser objects from each file. euconfigs = [] self.configfiles = configfiles try: for configfile in configfiles: euconfigs.append(EuConfig(filename=configfile)) except Exception, e: self.debug("Unable to read config from file: " + str(e)) for conf in euconfigs: cblocks = copy.copy(confblocks) #if MEMO field in our config block add it first if to set least precedence if 'MEMO' in cblocks: if conf.config.has_section('MEMO'): for item in conf.config.items('MEMO'): cf.__setattr__(str(item[0]), item[1]) cblocks.remove('MEMO') #If globals are still in our confblocks, add globals first if the section is present in config if 'globals' in cblocks: if conf.config.has_section('globals'): for item in conf.config.items('globals'): cf.__setattr__(str(item[0]), item[1]) cblocks.remove('globals') #Now iterate through remaining config block in file and add to args... for section in confblocks: if conf.config.has_section(section): for item in conf.config.items(section): cf.__setattr__(str(item[0]), item[1]) if cliargs: #Now make sure any conflicting args provided on the command line take precedence over config file args for val in cliargs._get_kwargs(): if (not val[0] in cf ) or (val[1] is not None): cf.__setattr__(str(val[0]), val[1]) args = cf #Legacy script support: level set var names for config_file vs configfile vs config and credpath vs cred_path try: if 'config' in args: args.config_file = args.config args.configfile = args.config except: pass try: args.cred_path = args.credpath except: pass self.args = args #finally add the namespace args to args for populating other testcase objs from this one if not self.has_arg('args'): args.__setattr__('args',copy.copy(args)) self.show_self() return args def get_default_userhome_config(self,fname='eutester.conf'):
[docs] ''' Description: Attempts to fetch the file 'fname' from the current user's home dir. Returns path to the user's home dir default eutester config file. :type fname: string :param fname: the eutester default config file name :rtype: string :returns: string representing the path to 'fname', the default eutester conf file. ''' try: def_path = os.getenv('HOME')+'/.eutester/'+str(fname) except: return None try: os.stat(def_path) return def_path except: self.debug("Default config not found:"+str(def_path)) return None def show_self(self):
[docs] list=[] list.append(("NAME:", str(self.name))) list.append(('TEST LIST:', str(self.testlist))) list.append(('CONFIG FILES:', self.configfiles)) argbuf="" argbuf = str("TESTCASE INFO:").ljust(25) argbuf += str("\n----------").ljust(25) for val in list: argbuf += '\n'+str(val[0]).ljust(25)+" --->: "+str(val[1]) self.status(argbuf) self.show_args() def show_args(self,args=None):
[docs] ''' Description: Prints args names and values for debug purposes. By default will use the local testcase.args, else args can be provided. :type args: namespace object :param args: namespace object to be printed,by default None will print local testcase's args. ''' args= args or self.args if hasattr(self,'args') else None argbuf = str("TEST ARGS:").ljust(25)+" "+str("VALUE:") argbuf += str("\n----------").ljust(25)+" "+str("------") if args: for val in args._get_kwargs(): argbuf += '\n'+str(val[0]).ljust(25)+" --->: "+str(val[1]) self.status(argbuf) def populate_testunit_with_args(self,testunit,namespace=None):
[docs] ''' Description: Checks a given test unit's available positional and key word args lists for matching values contained with the given namespace, by default will use local testcase.args. If testunit's underlying method has arguments matching the namespace provided, then those args will be applied to the testunits args referenced when running the testunit. Namespace values will not be applied/overwrite testunits, if the testunit already has conflicting values in it's args(positional) list or kwargs(keyword args) dict. :type: testunit: Eutestcase.eutestertestunit object :param: testunit: A testunit object for which the namespace values will be applied :type: namespace: namespace obj :param: namespace: namespace obj containing args/values to be applied to testunit. None by default will use local testunit args. ''' self.debug("Attempting to populate testunit:"+str(testunit.name)+", with testcase.args...") args_to_apply = namespace or self.args if not args_to_apply: return testunit_obj_args = {} #copy the test units key word args testunit_obj_args.update(copy.copy(testunit.kwargs)) self.debug("Testunit keyword args:"+str(testunit_obj_args)) #Get all the var names of the underlying method the testunit is wrapping method_args = self.get_meth_arg_names(testunit.method) offset = 0 if isinstance(testunit.method,types.FunctionType) else 1 self.debug("Got method args:"+str(method_args)) #Add the var names of the positional args provided in testunit.args to check against later #Append to the known keyword arg list for x,arg in enumerate(testunit.args): testunit_obj_args[method_args[x+offset]] = arg self.debug("test unit total args:"+str(testunit_obj_args)) #populate any global args which do not conflict with args already contained within the test case #first populate matching method args with our global testcase args taking least precedence for apply_val in args_to_apply._get_kwargs(): for methvar in method_args: if methvar == apply_val[0]: self.debug("Found matching arg for:"+str(methvar)) #Don't overwrite existing testunit args/kwargs that have already been assigned if apply_val[0] in testunit_obj_args: self.debug("Skipping populate because testunit already has this arg:"+str(methvar)) continue #Append cmdargs list to testunits kwargs testunit.set_kwarg(methvar,apply_val[1]) #testunit.kwargs[methvar]=apply_val[1] def do_with_args(self, meth, *args, **kwargs):
[docs] ''' Description: Convenience method used to wrap the provided instance_method, function, or object type 'meth' and populate meth's positional and keyword arguments with the local testcase.args created from the CLI and/or config file, as well as the *args and **kwargs variable length arguments passed into this method. :type meth: method :param meth: A method or class initiator to wrapped/populated with this testcase objects namespace args :type args: positional arguments :param args: None or more values representing positional arguments to be passed to 'meth' when executed. These will take precedence over local testcase obj namespace args :type kwargs: keyword arguments :param kwargs: None or more values reprsenting keyword arguments to be passed to 'meth' when executed. These will take precedence over local testcase obj namespace args and positional args ''' if not hasattr(self,'args'): raise Exception('TestCase object does not have args yet, see: get_args and setup_parser options') tc_args = self.args cmdargs={} f_code = self.get_method_fcode(meth) vars = self.get_meth_arg_names(meth) self.debug("do_with_args: Method:"+str(f_code.co_name)+", Vars:"+str(vars)) #first populate matching method args with our global testcase args... for val in tc_args._get_kwargs(): for var in vars: if var == val[0]: cmdargs[var]=val[1] #Then overwrite/populate with any given positional local args... for count,arg in enumerate(args): cmdargs[vars[count+1]]=arg #Finall overwrite/populate with any given key word local args... for name,value in kwargs.items(): for var in vars: if var == name: cmdargs[var]=value self.debug('create_with_args: running '+str(f_code.co_name)+"("+str(cmdargs).replace(':','=')+")") return meth(**cmdargs) @classmethod
def get_method_fcode(cls, meth):
[docs] f_code = None #Find the args for the method passed in... #Check for object/class init... if isinstance(meth,types.ObjectType): try: f_code = meth.__init__.__func__.func_code except:pass #Check for instance method... if isinstance(meth,types.MethodType): try: f_code = meth.im_func.func_code except:pass #Check for function... if isinstance(meth,types.FunctionType): try: f_code = meth.func_code except:pass if not f_code: raise Exception("get_method_fcode: Could not find function_code for passed method of type:"+str(type(meth))) return f_code @classmethod
def get_meth_arg_names(cls,meth):
[docs] ''' Description: Return varnames within argcount :type:meth: method :param: meth: method to fetch arg names for :rtype: list :returns: list of strings representing the varnames within argcount for this method ''' fcode = cls.get_method_fcode(meth) varnames = fcode.co_varnames[0:fcode.co_argcount] return varnames

Contents