class Application(object): def __init__(self, application_config, entry_points=['zaf.addons', 'zaf.local_addons'], signalhandler=None): root_logger = logging.getLogger() # Default config for rootlogger to not spam until logger is correctly configured root_logger.setLevel(logging.INFO) self.app_config = application_config self.signalhandler = signalhandler self.entry_points = entry_points self.extension_manager = ExtensionManager() self.component_manager = ComponentManager() self.component_factory = Factory(self.component_manager) self.session_scope = self.component_factory.enter_scope('session') self.messagebus = MessageBus(self.component_factory, self.session_scope) self.messagebus.define_endpoints_and_messages( {APPLICATION_ENDPOINT: [BEFORE_COMMAND, AFTER_COMMAND]}) self.config = ConfigManager() self.command = None self._exit_code = 1 self.app_config.apply_configuration(self.config) @component(name='MessageBus', scope='session') def messagebus(): """ Access the message bus. The message bus can be used to register dispatchers to specific endpoints, message_ids and entities and to send requests and events to the registered dispatchers. """ return self.messagebus @component(name='ComponentFactory', scope='session') def component_factory(): """ Access the component factory. The component factory can be used to call callables inside a scope and have the correct components instantiated in the scope. """ return self.component_factory @component(name='ComponentManager', scope='session') def component_manager(): """ Access the component manager. The component manager can be used to find out what components are available. """ return self.component_manager @component(name='Config', scope='session') def config(): """ Access the Config Manager. The Config components gives full access to all the config. """ return self.config @component(name='ExtensionManager', scope='session') def extension_manager(): """ Access the extension manager. The extension manager can be used to find out what extensions are loaded. """ return self.extension_manager def run(self): with self as instance: instance._exit_code = instance.execute_command() return instance._exit_code def setup(self): application_config_options = [ ConfigOption(MESSAGEBUS_TIMEOUT, required=False), ConfigOption(CWD, required=True), ] loader = ExtensionLoader(self.extension_manager, self.config, self.messagebus, application_config_options, self.component_manager) self.command = loader.load_extensions(self.entry_points) def teardown(self): try: self.messagebus.wait_for_not_active( timeout=self.config.get(MESSAGEBUS_TIMEOUT)) except MessageBusTimeout as e: logger.critical( 'Waiting for messagebus to be inactive timed out,' ' shutting down anyway. State: {state}'.format(state=e)) finally: logger.debug( 'Dispatchers still registered to the messagebus: {dispatchers}' .format(dispatchers=self.messagebus.get_dispatchers())) self.extension_manager.destroy() for th in threading.enumerate(): try: if th.name != 'MainThread': logger.debug(th) logger.debug('\n'.join( traceback.format_stack( sys._current_frames()[th.ident]))) except KeyError: logger.debug(th) def execute_command(self): logger.debug( 'Executing command {command} for application {application} with version {version}' .format(command=self.command.name, application=self.app_config.name, version=self.app_config.version)) self._activate_signalhandler() self.messagebus.trigger_event(BEFORE_COMMAND, APPLICATION_ENDPOINT, data=self.command.name) result = 0 try: result = self.component_factory.call(self.command.callable, self.session_scope, self) return result if result is not None else 0 finally: logger.debug( 'Command {command} exited with exit code {result}'.format( command=self.command.name, result=result).format(command=self.command.name, application=self.app_config.name, version=self.app_config.version)) self.component_factory.exit_scope(self.session_scope) self.messagebus.trigger_event(AFTER_COMMAND, APPLICATION_ENDPOINT, data=self.command.name) def gather_metadata(self, metadata_filter=None): return ZafMetadata(self.extension_manager.all_extensions, self.component_manager.get_components_info(), self.config, self.app_config.entrypoint, self.extension_manager, metadata_filter) def __enter__(self): try: self.setup() except Exception: self.__exit__(*sys.exc_info()) raise return self def __exit__(self, *args): self.teardown() @property def exit_code(self): return self._exit_code def _activate_signalhandler(self): if self.signalhandler is not None: self.signalhandler.activate(self.messagebus) def _deactivate_signalhandler(self): if self.signalhandler is not None: self.signalhandler.deactivate()
class TestWaitForLine(unittest.TestCase): def setUp(self): self.messagebus = MessageBus(Factory(ComponentManager({}))) self.endpoint = MOCK_ENDPOINT self.sut = Mock() self.sut.entity = 'entity' self.config = Mock() self.config.get = MagicMock(side_effect=config_get_log_sources) self.messagebus.define_endpoints_and_messages( {self.endpoint: [LOG_LINE_RECEIVED]}) def test_wait_for_line_times_out(self): sut_events = SutEvents(self.messagebus, self.config, self.sut) with sut_events.wait_for_log_line(r'not matching') as lines: self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-entity', data='line') with self.assertRaises(NoMatchingLogLine): lines.get(timeout=0) def test_wait_for_line_matches_first_line(self): [match] = list(self.wait_for_lines(r'li\w+', ['line'], 1)) self.assertEqual(match.string, 'line') def test_wait_for_line_matches_some_lines(self): [match1, match2] = list( self.wait_for_lines(r'match\d+', ['match1', 'no match', 'match2', 'no match'], 2)) self.assertEqual(match1.string, 'match1') self.assertEqual(match2.string, 'match2') def test_wait_for_line_matches_with_match_groups(self): [match] = list(self.wait_for_lines(r'(\S+) (\S+)', ['first second'], 1)) self.assertEqual(match.group(1), 'first') self.assertEqual(match.group(2), 'second') def test_wait_for_line_matches_with_named_match_groups(self): [match] = list( self.wait_for_lines(r'(?P<first>\S+) (?P<second>\S+)', ['first second'], 1)) self.assertEqual(match.group('first'), 'first') self.assertEqual(match.group('second'), 'second') def test_wait_for_line_matches_when_match_is_not_at_start_of_line(self): [match] = list( self.wait_for_lines(r'(?P<second>second)', ['first second'], 1)) self.assertEqual(match.string, 'first second') self.assertEqual(match.group('second'), 'second') def wait_for_lines(self, log_line_regex, lines, expected_matches): sut_events = SutEvents(self.messagebus, self.config, self.sut) with sut_events.wait_for_log_line(log_line_regex) as received_lines: for line in lines: self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-entity', data=line) return [ received_lines.get(timeout=0) for _ in range(0, expected_matches) ] def test_get_all_lines(self): lines = ['first A', 'second B', 'third A', 'fourth B'] regex = r'A' matching_lines = ['first A', 'third A'] sut_events = SutEvents(self.messagebus, self.config, self.sut) with sut_events.wait_for_log_line(regex) as queue: for line in lines: self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-entity', data=line) self.messagebus.wait_for_not_active() matches = queue.get_all() strings = [match.string for match in matches] self.assertEqual(strings, matching_lines) def test_wait_for_line_raises_no_log_sources_error_if_log_source_is_unavailable( self): sut_events = SutEvents(self.messagebus, self.config, self.sut) with self.assertRaises(NoLogSources): sut_events.wait_for_log_line( r'not matching', log_sources='log-source-unavailable').__enter__() def test_wait_for_line_raises_no_log_sources_error_if_no_log_sources_defined_for_sut( self): config = Mock() config.get = MagicMock(return_value=[]) sut_events = SutEvents(self.messagebus, config, self.sut) with self.assertRaises(NoLogSources): sut_events.wait_for_log_line( r'not matching', log_sources='not-a-log-source').__enter__() def test_wait_for_line_matches_only_one_log_source(self): lines = ['first A', 'second B', 'third A', 'fourth B'] regex = r'B' matching_lines = ['second B', 'fourth B'] config = Mock() config.get = MagicMock(return_value=['log-source-A', 'log-source-B']) sut_events = SutEvents(self.messagebus, config, self.sut) with sut_events.wait_for_log_line(regex, log_sources='log-source-B') as queue: self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-source-A', data=lines[0]) self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-source-B', data=lines[1]) self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-source-A', data=lines[2]) self.messagebus.trigger_event(LOG_LINE_RECEIVED, self.endpoint, entity='log-source-B', data=lines[3]) self.messagebus.wait_for_not_active() matches = queue.get_all() strings = [match.string for match in matches] self.assertEqual(strings, matching_lines)