Commit b77d6bdd authored by Matthieu Muffato's avatar Matthieu Muffato Committed by ens-bwalts

Added some comments and some documentation

parent ad5435ee
......@@ -35,23 +35,55 @@ FailureEvent = collections.namedtuple('FailureEvent', ['exception', 'args'])
def testRunnable(testcase, runnableClass, inputParameters, refEvents, config=None):
"""Method to test a Runnable"""
"""Method to test a Runnable
Args:
testcase: instance of unittest.TestCase, which is used to do the actual tests.
runnableClass: Runnable being tested. Can be a string of the actual type.
inputParameters: dictionary of input parameters. Will override the Runnable's
param_defaults() dictionary.
refEvents: list of "events" the Runnable is expected to raise (in the right
order). Accepted events are
- WarningEvent.
- DataflowEvent.
- CompleteEarlyEvent.
- FailureEvent.
config: extra configuration options, given as a dictionary. Accepted keys are
- is_retry: bool or int, default False.
whether the job is considered a retry (i.e. whether
pre_cleanup should run).
- execute_writes: bool or int, default True.
whether write_output is run.
- test_autoflow: bool, default not set.
when set, check that this is the final value of the
job's autoflow attribute.
- test_lethal_for_worker: bool, default not set.
when set, check that this is the final value
of the job's lethal_for_worker attribute.
- test_transient_error: bool, default not set.
when set, check that this is the final value
of the job's lethal_for_worker attribute.
"""
# Find the actual class (type)
if isinstance(runnableClass, str):
runnableClass = find_module(runnableClass)
class RunnableTester(runnableClass):
"""Helper class to provide a test-enabled version of the requested Runnable"""
def runTests(self):
"""Entry point of RunnableTester. Run everything in order"""
self.__configure()
self.__job_life_cycle()
self.__final_tests()
def __configure(self):
"""Initialise all the parameters the Runnable may need"""
# Copy all the input parameters in the class instance itself
self.__config = config or {}
self.__refEvents = refEvents.copy()
self.__refEvents = refEvents.copy() # Don't modify the original list
# Build the parameter hash
paramsDict = {}
......@@ -71,6 +103,7 @@ def testRunnable(testcase, runnableClass, inputParameters, refEvents, config=Non
self.input_job = job
def __job_life_cycle(self):
"""Run the job's life cycle. This must match BaseRunnable.__job_life_cycle"""
# Which methods should be run
steps = ['fetch_input', 'run']
......@@ -80,12 +113,14 @@ def testRunnable(testcase, runnableClass, inputParameters, refEvents, config=Non
steps.append('write_output')
steps.append('post_healthcheck')
# We need to manager the temp directory since GuestProcess/Worker are not around
self.__created_worker_temp_directory = None
try:
for s in steps:
self.__run_method_if_exists(s)
except CompleteEarlyException as e:
# CompleteEarlyException must be declared in the test plan
event = CompleteEarlyEvent(e.args[0] if e.args else None)
self.__compare_next_event(event)
except Exception as e:
......@@ -99,11 +134,14 @@ def testRunnable(testcase, runnableClass, inputParameters, refEvents, config=Non
self.__cleanup_worker_temp_directory()
def __run_method_if_exists(self, method):
"""method is one of "pre_cleanup", "fetch_input", "run", "write_output", "post_cleanup"."""
"""Run the method (one of "fetch_input", "run", "write_output",
etc) if defined in the Runnable."""
if hasattr(self, method):
getattr(self, method)()
def __handle_exception(self, e):
"""Capture and check the Runnable's own exceptions whilst letting
the testcase's exceptions pass through"""
if any(f for f in traceback.extract_tb(e.__traceback__) if f[2] == '__compare_next_event'):
raise e
else:
......@@ -112,25 +150,30 @@ def testRunnable(testcase, runnableClass, inputParameters, refEvents, config=Non
self.__compare_next_event(event)
def __final_tests(self):
"""Extra tests once the job has ended"""
testcase.assertFalse(self.__refEvents, msg='The job has now ended and {} events have not been emitted'.format(len(self.__refEvents)))
# Job attributes that the Runnable could have set
# Job attributes that the Runnable could have set and we want to test
for attr in ['autoflow', 'lethal_for_worker', 'transient_error']:
tattr = "test_" + attr
if tattr in self.__config:
testcase.assertEqual(getattr(self.input_job, attr), self.__config[tattr], msg='Final value of {}'.format(attr))
# Public BaseRunnable interface
################################
# Overridden BaseRunnable interface
###################################
def worker_temp_directory(self):
"""Provide a temporary directory for the duration of the test"""
"""Provide a temporary directory for the duration of the test.
This functionality was handled by the Perl side (via GuestProcess
but has to be reimplemented."""
if self.__created_worker_temp_directory is None:
self.__created_worker_temp_directory = tempfile.mkdtemp()
return self.__created_worker_temp_directory
def __cleanup_worker_temp_directory(self):
"""Provide a temporary directory for the duration of the test"""
"""Remove the temporary directory created by worker_temp_directory.
Again, this was handled by the Perl side but has to be
reimplemented."""
if self.__created_worker_temp_directory:
shutil.rmtree(self.__created_worker_temp_directory)
......@@ -148,6 +191,8 @@ def testRunnable(testcase, runnableClass, inputParameters, refEvents, config=Non
return [1]
def __compare_next_event(self, event):
"""Helper method for warning and dataflow.
Check that the event that has been generated is expected."""
testcase.assertTrue(self.__refEvents, msg='No more events are expected but {} was raised'.format(event))
testcase.assertEqual(event, self.__refEvents.pop(0))
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment