mirror of https://github.com/mongodb/mongo
130 lines
4.8 KiB
Python
130 lines
4.8 KiB
Python
"""Base class for a hook that runs a thread in the background."""
|
|
|
|
import math
|
|
import threading
|
|
|
|
from buildscripts.resmokelib.testing.hooks import interface
|
|
|
|
|
|
class BGJob(threading.Thread):
|
|
"""
|
|
Background job that continuously calls the 'run_action' function of the given hook.
|
|
|
|
BGJob will call 'run_action' without any delay and expects the 'run_action' function to add some form of delay.
|
|
"""
|
|
|
|
def __init__(self, hook, loop_delay_ms=None):
|
|
"""Initialize the background job."""
|
|
threading.Thread.__init__(self, name=f"BGJob-{hook.__class__.__name__}")
|
|
self._loop_delay_ms = loop_delay_ms
|
|
self.daemon = True
|
|
self._hook = hook
|
|
self._interrupt_event = threading.Event()
|
|
self.__is_alive = True
|
|
self.err = None
|
|
|
|
def run(self):
|
|
"""Run the background job."""
|
|
while True:
|
|
if self.__is_alive is False:
|
|
break
|
|
|
|
try:
|
|
self._hook.run_action()
|
|
if self._loop_delay_ms is not None:
|
|
# The configured loop delay asked us to wait before running the action again. Do
|
|
# that wait, but listen to see if we finish running the test or are killed in
|
|
# the meantime.
|
|
interrupted = self._interrupt_event.wait(self._loop_delay_ms / 1000.0)
|
|
if interrupted:
|
|
self._hook.logger.info("interrupted")
|
|
break
|
|
except Exception as err:
|
|
self._hook.logger.error("Background thread caught exception: %s.", err)
|
|
self.err = err
|
|
self.__is_alive = False
|
|
|
|
def kill(self):
|
|
"""Kill the background job."""
|
|
self.__is_alive = False
|
|
self._interrupt_event.set()
|
|
|
|
|
|
class BGHook(interface.Hook):
|
|
"""A hook that repeatedly calls run_action() in a background thread for the duration of the test suite."""
|
|
|
|
IS_BACKGROUND = True
|
|
# By default, we continuously run the background hook for the duration of the suite.
|
|
DEFAULT_TESTS_PER_CYCLE = math.inf
|
|
|
|
def __init__(self, hook_logger, fixture, desc, tests_per_cycle=None, loop_delay_ms=None):
|
|
"""
|
|
Initialize the background hook.
|
|
|
|
'tests_per_cycle' or 'loop_delay_ms' can be used to configure how often the background job
|
|
is restarted, and how often run_action() is called, respectively.
|
|
"""
|
|
interface.Hook.__init__(self, hook_logger, fixture, desc)
|
|
|
|
self.logger = hook_logger
|
|
self.running_test = None
|
|
self._background_job = None
|
|
|
|
self._test_num = 0
|
|
# The number of tests we execute before restarting the background hook.
|
|
self._tests_per_cycle = (
|
|
self.DEFAULT_TESTS_PER_CYCLE if tests_per_cycle is None else tests_per_cycle
|
|
)
|
|
self._loop_delay_ms = loop_delay_ms
|
|
|
|
def run_action(self):
|
|
"""
|
|
Perform an action. This function will be called continuously in the BgJob.
|
|
|
|
If a sleep_delay_ms was given, that many milliseconds of sleep will happen between each
|
|
invocation.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def before_suite(self, test_report):
|
|
"""Start the background thread."""
|
|
self.logger.info("Starting the background thread.")
|
|
self._background_job = BGJob(self, self._loop_delay_ms)
|
|
self._background_job.start()
|
|
|
|
def after_suite(self, test_report, teardown_flag=None):
|
|
"""Signal the background thread to exit, and wait until it does."""
|
|
|
|
self.logger.info("Stopping the background thread.")
|
|
self._background_job.kill()
|
|
self._background_job.join()
|
|
|
|
if self._background_job.err is not None:
|
|
self.logger.error("Encountered an error inside the hook: %s.", self._background_job.err)
|
|
raise self._background_job.err
|
|
|
|
def before_test(self, test, test_report):
|
|
"""Each test will call this before it executes."""
|
|
self.running_test = test
|
|
if self._background_job.is_alive():
|
|
return
|
|
|
|
self.logger.info("Restarting the background thread.")
|
|
self._background_job = BGJob(self, self._loop_delay_ms)
|
|
self._background_job.start()
|
|
|
|
def after_test(self, test, test_report):
|
|
"""Each test will call this after it executes. Check if the hook found an error."""
|
|
self._test_num += 1
|
|
if self._test_num % self._tests_per_cycle != 0 and self._background_job.err is None:
|
|
return
|
|
|
|
self._background_job.kill()
|
|
self._background_job.join()
|
|
|
|
if self._background_job.err is not None:
|
|
self.logger.error("Encountered an error inside the hook: %s.", self._background_job.err)
|
|
raise self._background_job.err
|
|
else:
|
|
self.logger.info("Reached end of cycle in the hook, killing background thread.")
|