From: JINMEI Tatuya Date: Tue, 19 Feb 2013 03:06:08 +0000 (-0800) Subject: [2689] eliminate threads in test_start. X-Git-Tag: bind10-1.1.0beta1-release~76^2~15 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=146d0e9b8a417220a9a9eeb9adb4c2ccb66db178;p=thirdparty%2Fkea.git [2689] eliminate threads in test_start. some refactoring to Stats class (behavior shouldn't change) and mock classes were introduced. shutdown case was extracted to a spearate test function. --- diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in index 0af09337ee..0cb10d7efe 100755 --- a/src/bin/stats/stats.py.in +++ b/src/bin/stats/stats.py.in @@ -190,12 +190,19 @@ class Stats: """ Main class of stats module """ - def __init__(self): + def __init__(self, module_ccsession_class=isc.config.ModuleCCSession): + '''Constructor + + module_ccsession_class is parameterized so that test can specify + a mocked class to test the behavior without involing network I/O. + In other cases this parameter shouldn't be specified. + + ''' self.running = False # create ModuleCCSession object - self.mccs = isc.config.ModuleCCSession(SPECFILE_LOCATION, - self.config_handler, - self.command_handler) + self.mccs = module_ccsession_class(SPECFILE_LOCATION, + self.config_handler, + self.command_handler) self.cc_session = self.mccs._session # get module spec self.module_name = self.mccs.get_module_spec().get_module_name() @@ -225,7 +232,16 @@ class Stats: ]) # set a absolute timestamp polling at next time self.next_polltime = get_timestamp() + self.get_interval() - # initialized Statistics data + + self._init_statistics_data() + + def _init_statistics_data(self): + """initialized Statistics data. + + This method is a dedicated subroutine of __int__(), but extracted + so tests can override it to avoid blocking network operation. + + """ self.update_modules() if self.update_statistics_data( self.module_name, @@ -338,31 +354,35 @@ class Stats: # if successfully done, set the last time of polling self._lasttime_poll = get_timestamp() + def _check_command(self, nonblock=False): + """check invoked command by waiting for 'poll-interval' seconds + + This is a dedicated subroutine of start(), but extracted and defined + as a 'protected' method so that tests can replace it. + + """ + # backup original timeout + orig_timeout = self.cc_session.get_timeout() + # set cc-session timeout to half of a second(500ms) + self.cc_session.set_timeout(500) + try: + answer, env = self.cc_session.group_recvmsg(nonblock) + self.mccs.check_command_without_recvmsg(answer, env) + except isc.cc.session.SessionTimeout: + pass # waited for poll-interval seconds + # restore timeout + self.cc_session.set_timeout(orig_timeout) + def start(self): """ Start stats module """ logger.info(STATS_STARTING) - def _check_command(nonblock=False): - """check invoked command by waiting for 'poll-interval' - seconds""" - # backup original timeout - orig_timeout = self.cc_session.get_timeout() - # set cc-session timeout to half of a second(500ms) - self.cc_session.set_timeout(500) - try: - answer, env = self.cc_session.group_recvmsg(nonblock) - self.mccs.check_command_without_recvmsg(answer, env) - except isc.cc.session.SessionTimeout: - pass # waited for poll-interval seconds - # restore timeout - self.cc_session.set_timeout(orig_timeout) - try: self.running = True while self.running: - _check_command() + self._check_command() now = get_timestamp() intval = self.get_interval() if intval > 0 and now >= self.next_polltime: diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py index cb2e101e29..2bbaa6019f 100644 --- a/src/bin/stats/tests/b10-stats_test.py +++ b/src/bin/stats/tests/b10-stats_test.py @@ -32,7 +32,8 @@ import sys import stats import isc.log import isc.cc.session -from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command +from test_utils import BaseModules, ThreadingServerManager, MyStats, \ + SignalHandler, MyModuleCCSession, send_command from isc.testutils.ccsession_mock import MockModuleCCSession class TestUtilties(unittest.TestCase): @@ -287,29 +288,51 @@ class TestStats(unittest.TestCase): self.assertRaises(stats.StatsError, stats.Stats) stats.SPECFILE_LOCATION = orig_spec_location + class SimpleStat(stats.Stats): + def __init__(self): + stats.Stats.__init__(self, MyModuleCCSession) + + def _init_statistics_data(self): + pass + + def __send_command(self, stats, command_name, params=None): + '''Emulate a command arriving to stats by directly calling callback''' + return isc.config.ccsession.parse_answer( + stats.command_handler(command_name, params)) + def test_start(self): + # Define a separate exception class so we can be sure that's actually + # the one raised in __check_start() below + class CheckException(Exception): + pass + + def __check_start(tested_stats): + self.assertTrue(tested_stats.running) + raise CheckException # terminate the loop + # start without err - self.stats_server = ThreadingServerManager(MyStats) - self.stats = self.stats_server.server - self.assertFalse(self.stats.running) - self.stats_server.run() - self.assertEqual(send_command("status", "Stats"), - (0, "Stats is up. (PID " + str(os.getpid()) + ")")) - self.assertTrue(self.stats.running) - # Due to a race-condition related to the threads used in these - # tests, use of the mock session and the stopped check (see - # below), are temporarily disabled - # See ticket #1668 - # Override moduleCCSession so we can check if send_stopping is called - #self.stats.mccs = MockModuleCCSession() - self.assertEqual(send_command("shutdown", "Stats"), (0, None)) - self.assertFalse(self.stats.running) - # Call server.shutdown with argument True so the thread.join() call - # blocks and we are sure the main loop has finished (and set - # mccs.stopped) - self.stats_server.shutdown(True) - # Also temporarily disabled for #1668, see above - #self.assertTrue(self.stats.mccs.stopped) + stats = self.SimpleStat() + self.assertFalse(stats.running) + stats._check_command = lambda: __check_start(stats) + # We are going to confirm start() will set running to True, avoidng + # to fall into a loop with the exception trick. + self.assertRaises(CheckException, stats.start) + self.assertEqual(self.__send_command(stats, "status"), + (0, "Stats is up. (PID " + str(os.getpid()) + ")")) + + def test_shutdown(self): + def __check_shutdown(tested_stats): + self.assertTrue(tested_stats.running) + self.assertEqual(self.__send_command(tested_stats, "shutdown"), + (0, None)) + self.assertFalse(tested_stats.running) + # override get_interval() so it won't go poll statistics + tested_stats.get_interval = lambda : 0 + + stats = self.SimpleStat() + stats._check_command = lambda: __check_shutdown(stats) + stats.start() + self.assertTrue(stats.mccs.stopped) def test_handlers(self): self.stats_server = ThreadingServerManager(MyStats) diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py index 9974f745cf..2744522d10 100644 --- a/src/bin/stats/tests/test_utils.py +++ b/src/bin/stats/tests/test_utils.py @@ -455,6 +455,26 @@ class MockAuth: return isc.config.create_answer(0, sdata) return isc.config.create_answer(1, "Unknown Command") +class MyModuleCCSession(isc.config.ConfigData): + """Mocked ModuleCCSession class. + + This class incorporates the module spec directly from the file, + and works as if the ModuleCCSession class as much as possible + without involving network I/O. + + """ + def __init__(self, spec_file, config_handler, command_handler): + module_spec = isc.config.module_spec_from_file(spec_file) + isc.config.ConfigData.__init__(self, module_spec) + self._session = self + self.stopped = False + + def start(self): + pass + + def send_stopping(self): + self.stopped = True # just record it's called to inspect it later + class MyStats(stats.Stats): stats._BASETIME = CONST_BASETIME