def test_run_to_idle(self): b = ProcessBus() b.transition('RUN') self.log(b, level=20) try: self.responses = [] num = 3 for index in range(num): b.subscribe('STOP', self.get_listener('STOP', index)) b.transition('IDLE') # The idle transition MUST call all 'stop' listeners. self.assertEqual( set(self.responses), set(msg % (i, 'STOP', None) for i in range(num)) ) # The idle method MUST move the state to IDLE self.assertEqual(b.state, 'IDLE') # The idle method MUST log its states. self.assertLog([ 'Bus state: STOP', 'Bus state: IDLE' ]) finally: # Exit so the atexit handler doesn't complain. b.transition('EXITED')
def test_keyboard_interrupt(self): bus = ProcessBus() Handler.bus = bus service = WebService(address=('127.0.0.1', 38002), handler_class=Handler) adapter = servers.ServerPlugin(bus, service, service.address) adapter.subscribe() # Raise a keyboard interrupt in the HTTP server's main thread. bus.transition('RUN') resp = service.do_GET('/ctrlc') assertEqual(resp.status, 200) bus.block() assertEqual(bus.state, 'EXITED')
def test_block(self): b = ProcessBus() self.log(b) def f(): time.sleep(0.2) b.transition('EXITED') def g(): time.sleep(0.4) def main_listener(): main_calls.append(1) main_calls = [] b.subscribe("main", main_listener) f_thread = threading.Thread(target=f, name='f') f_thread.start() threading.Thread(target=g, name='g').start() threads = [t for t in threading.enumerate() if not t.daemon] self.assertEqual(len(threads), 3) b.block() f_thread.join() # The block method MUST wait for the EXITED state. self.assertEqual(b.state, 'EXITED') # The block method MUST wait for ALL non-main, non-daemon threads to # finish. threads = [t for t in threading.enumerate() if not t.daemon] self.assertEqual(len(threads), 1) # The last message will mention an indeterminable thread name; ignore # it self.assertEqual( [entry for entry in self._log_entries if not entry.startswith('Publishing') and not entry.startswith('Waiting')], [ 'Bus state: ENTER', 'Bus state: IDLE', 'Bus state: EXIT', 'Bus state: EXITED' ] ) # While the bus was blocked, it should have published periodically # to the "main" channel. self.assertGreater(len(main_calls), 0)
def test_idle_to_exit(self): b = ProcessBus() self.log(b, level=20) self.responses = [] num = 3 for index in range(num): b.subscribe('EXIT', self.get_listener('EXIT', index)) b.subscribe('EXITED', self.get_listener('EXITED', index)) b.transition('EXITED') # The bus MUST call all 'EXIT' listeners, # and then all 'EXITED' listeners. self.assertEqual( set(self.responses), set([msg % (i, 'EXIT', None) for i in range(num)] + [msg % (i, 'EXITED', None) for i in range(num)]) ) # The bus MUST move the state to EXITED self.assertEqual(b.state, 'EXITED') # The bus MUST log its states. self.assertLog([ 'Bus state: ENTER', 'Bus state: IDLE', 'Bus state: EXIT', 'Waiting for child threads to terminate...', 'Bus state: EXITED' ])
def test_idle_to_run(self): b = ProcessBus() self.log(b, level=20) self.responses = [] num = 3 for index in range(num): b.subscribe('START', self.get_listener('START', index)) b.transition('RUN') try: # The start method MUST call all 'start' listeners. self.assertEqual( set(self.responses), set([msg % (i, 'START', None) for i in range(num)]) ) # The transition method MUST move the state to RUN # (or START_ERROR, if errors occur) self.assertEqual(b.state, 'RUN') # The start method MUST log its states. self.assertLog([ 'Bus state: ENTER', 'Bus state: IDLE', 'Bus state: START', 'Bus state: RUN' ]) finally: # Exit so the atexit handler doesn't complain. b.transition('EXITED')
def test_wait(self): b = ProcessBus() self.log(b) def f(desired_state): time.sleep(0.2) b.transition(desired_state) for desired_state_, states_to_wait_for in [ ('RUN', ['RUN']), ('IDLE', ['IDLE']), ('RUN', ['START', 'RUN']), ('EXITED', ['EXITED']) ]: threading.Thread(target=f, args=(desired_state_,)).start() b.wait(states_to_wait_for) # The wait method MUST wait for the given state(s). if b.state not in states_to_wait_for: self.fail('State %r not in %r' % (b.state, states_to_wait_for))
def test_start_with_callback(self): b = ProcessBus() self.log(b) try: events = [] def f(*args, **kwargs): events.append(('f', args, kwargs)) def g(): events.append('g') b.subscribe('RUN', g) b.start_with_callback(f, (1, 3, 5), {'foo': 'bar'}) # Give wait() time to run f() time.sleep(0.2) # The callback method MUST wait for the STARTED state. self.assertEqual(b.state, 'RUN') # The callback method MUST run after all start methods. self.assertEqual(events, ['g', ('f', (1, 3, 5), {'foo': 'bar'})]) finally: b.transition('EXITED')
def test_builtin_channels(self): b = ProcessBus() listeners = [l for l in b.listeners if l != 'log' and not l.endswith('_ERROR')] self.responses, expected = [], [] for channel in listeners: if channel != 'log': for index, priority in enumerate([100, 50, 0, 51]): b.subscribe(channel, self.get_listener(channel, index), priority) try: for channel in listeners: if channel != 'log': b.publish(channel) expected.extend([msg % (i, channel, ()) for i in (2, 1, 3, 0)]) self.assertEqual(self.responses, expected) finally: # Exit so the atexit handler doesn't complain. b.transition('EXITED')
def test_block(self): b = ProcessBus() self.log(b) def f(): time.sleep(0.2) b.transition('EXITED') def g(): time.sleep(0.4) def main_listener(): main_calls.append(1) main_calls = [] b.subscribe('main', main_listener) f_thread = threading.Thread(target=f, name='f') f_thread.start() threading.Thread(target=g, name='g').start() threads = [t for t in threading.enumerate() if not t.daemon] assert len(threads) == 3 b.block() f_thread.join() # The block method MUST wait for the EXITED state. assert b.state == 'EXITED' # The block method MUST wait for ALL non-main, non-daemon threads to # finish. threads = [t for t in threading.enumerate() if not t.daemon] assert len(threads) == 1 # The last message will mention an indeterminable thread name; ignore # it assert ([ entry for entry in self._log_entries if not entry.startswith('Publishing') and not entry.startswith('Waiting') ] == [ 'Bus state: ENTER', 'Bus state: IDLE', 'Bus state: EXIT', 'Bus state: EXITED' ]) # While the bus was blocked, it should have published periodically # to the "main" channel. assert len(main_calls) > 0
def test_thread_manager(self): bus = ProcessBus() class Handler(WebHandler): def do_GET(self): if self.path == '/': self.respond('Hello World') else: self.respond(status=404) Handler.bus = bus service = WebService(address=('127.0.0.1', 38001), handler_class=Handler) WebAdapter(bus, service).subscribe() tm = tasks.ThreadManager(bus) tm.subscribe() assertEqual(len(tm.threads), 0) # Test server start bus.transition('RUN') try: assertEqual(bus.state, 'RUN') assertEqual(service.ready, True) assertEqual(len(tm.threads), 0) assertEqual(service.do_GET('/').read(), b'Hello World') assertEqual(len(tm.threads), 1) # Test bus stop. This will also stop the WebService. bus.transition('IDLE') assertEqual(bus.state, 'IDLE') # Verify that our custom stop function was called assertEqual(len(tm.threads), 0) finally: bus.transition('EXITED')