Source code for fmeTestSupport.TestCase

# Copyright (c) Fraunhofer MEVIS, Germany. All rights reserved.
# **InsertLicense** code author="Lennart Tautz"

import functools
import inspect
import sys
from TestSupport.TestHelper import CancelTestException
from fmeTestSupport.TestCenterTestCase import TestCenterTestCaseBase
import TestSupport.Logging as Logging

def _isNameOfTestFunction(name):
  return name.startswith("TEST") or name.startswith("DISABLED_TEST")

def _isNameOfIterativeTestFunction(name):
  return name.startswith("ITERATIVETEST")

def _isBoundMethodOf(fct, testCaseClass):
  isBoundTo = None
  if sys.version_info.major >= 3:
    isBoundTo = isinstance(fct.__self__, testCaseClass)
  else:
    isBoundTo = isinstance(fct.im_self, testCaseClass)
  return isBoundTo

def _callFunction(fct, *args, **kwargs):
  if sys.version_info.major >= 3:
    fct.__func__(*args, **kwargs)
  else:
    fct.im_func(*args, **kwargs)
  
[docs]def injectTestCase(testCaseClass, context): testFunctions, arguments = _createTestFunctionsFromTestCaseClass(testCaseClass) _populateContextWithTestFunctions(context, testCaseClass, testFunctions) _populateContextWithArguments(context, arguments)
[docs]def iterTestCenterTestCaseClasses( context ): """ Generator yielding all TestCenterTestCaseBase derived classes in the given context. """ for cls in context.values(): if inspect.isclass( cls ): if issubclass( cls, TestCenterTestCaseBase ): yield cls
[docs]def injectAllTestCenterTestCases( context ): """ Injects all test methods found in classes derived from TestCenterTestCaseBase into the given context (e.g. globals()). Will ignore test classes that are superclasses of other test classes in the given context to prevent duplicate test functions for test hierarchies. """ allTestClasses = list( iterTestCenterTestCaseClasses( context ) ) for cls in list( iterTestCenterTestCaseClasses( context ) ): if not _containsDerivedClasses( allTestClasses, cls ): injectTestCase( cls, context )
def _containsDerivedClasses( classes, superClass ): for c in classes: if issubclass( c, superClass ) and c is not superClass: return True return False def _createTestFunctionsFromTestCaseClass(testCaseClass): testFunctions = dict() arguments = dict() for testFctName in dir(testCaseClass): if _isNameOfTestFunction(testFctName): testFunction = _createTestFunction(testCaseClass, testFctName) testFunctions.update(testFunction) elif _isNameOfIterativeTestFunction(testFctName): newTestFunctions, newArguments = _createIterativeTestFunctions(testCaseClass, testFctName) testFunctions.update(newTestFunctions) arguments.update(newArguments) return testFunctions, arguments def _createTestFunction(testCaseClass, testFctName): testFunction = dict() potentialFunction = getattr(testCaseClass, testFctName) if callable(potentialFunction): potentialFunction.__name__ = testFctName potentialFunction._testCaseClass = testCaseClass testFunction[ testFctName ] = potentialFunction return testFunction def _createIterativeTestFunctions(testCaseClass, testFctName): testFunctions = dict() arguments = dict() potentialFunction = getattr(testCaseClass, testFctName) if not callable(potentialFunction): Logging.error('Potential iterative test function {} is not callable'.format(testFctName)) return dict(), dict() testCase = testCaseClass() cases, actualFunction = potentialFunction(testCase) if not callable(actualFunction): Logging.error('Function returned by iterative test {} is not callable'.format(testFctName)) return dict(), dict() iterativeTestName = testFctName.replace('ITERATIVETEST_', '') if not isinstance(cases, dict): try: cases = {k: v for k, v in enumerate(cases)} except TypeError: Logging.error('Iterative test {} did not return an iterable'.format(testFctName)) return dict(), dict() if inspect.ismethod(actualFunction) and _isBoundMethodOf(actualFunction, testCaseClass): Logging.warning('The callable returned by iterative test {} is a bound method, and will be redirected to a new instance of the test case class.'.format(testFctName)) for caseName, argsTuple in cases.items(): fullName = 'TEST_{}_{}'.format(iterativeTestName, caseName) testFunctions[fullName] = _createIteratedFunction(fullName, testCaseClass, actualFunction) arguments[fullName] = _createArguments(argsTuple) return testFunctions, arguments def _createIteratedFunction(fullName, testCaseClass, actualFunction): if inspect.isfunction(actualFunction): def containerFunction(*args, **kwargs): spec = inspect.getfullargspec(actualFunction) if spec.args and spec.args[0] == 'self': actualArgs = args else: actualArgs = args[1:] actualFunction(*actualArgs, **kwargs) else: def containerFunction(*args, **kwargs): _callFunction(actualFunction, *args, **kwargs) containerFunction.__name__ = fullName containerFunction._testCaseClass = testCaseClass return containerFunction def _createArguments(argsTuple): if not isinstance(argsTuple, tuple): argsTuple = (argsTuple,) return argsTuple def _populateContextWithTestFunctions(context, testCaseClass, testFunctions): STORED_SETUP_FUNCTION = 'STORED_setUpTestCase' SETUP_FUNCTION = 'setUpTestCase' if testFunctions: for fctName, fct in testFunctions.items(): context[fctName] = fct context['GROUP_{}'.format(testCaseClass.__name__)] = lambda: list(testFunctions.values()) hasAlreadyInjected = context.get('VISITED', False) if hasAlreadyInjected: originalSetupFunction = context.get(STORED_SETUP_FUNCTION, None) else: originalSetupFunction = context.get(SETUP_FUNCTION, None) context[STORED_SETUP_FUNCTION] = originalSetupFunction def _callSetUp(): # CAVEAT # Partial functions are not considered "callable" by MLABModule::callableFunctions, # so we have to wrap the function call functools.partial(__setUpTestCase, context)() def _callSetUpWithExisting(): originalSetupFunction() functools.partial(__setUpTestCase, context)() if originalSetupFunction: context[SETUP_FUNCTION] = _callSetUpWithExisting else: context[SETUP_FUNCTION] = _callSetUp context['VISITED'] = True def __setUpTestCase(context): for fctName, fct in context.items(): if _isNameOfTestFunction(fctName): if 'ARGUMENTS_{}'.format(fctName) in context: args = context['ARGUMENTS_{}'.format(fctName)] context[fctName] = functools.partial(_decorate(fct), args) else: context[fctName] = _decorate(fct) def _decorate( fct ): @functools.wraps( fct ) # used to copy function properties like docstring etc to wrapped function def decorated( *args, **kwargs ): testCase = fct._testCaseClass() testCase.setUp() try: if len(args) > 0: actualArgs = args[0] else: actualArgs = args fct(testCase, *actualArgs, **kwargs) except CancelTestException: raise except Exception: testCase.tearDown() raise else: testCase.tearDown() return decorated def _populateContextWithArguments(context, arguments): if arguments: for fctName, args in arguments.items(): context['ARGUMENTS_{}'.format(fctName)] = args