def test_update(self): one_conf_dict = { 'key1': 'value1', 1: 'value2', 2: { 1: 'some_value' }, } another_conf_dict = { 'key3': ('one', 'two', 'three'), 'key4': { 1: 'uno', 'key4-2': 'dos' }, } merged_conf_dicts = { 'key1': 'value1', 1: 'value2', 2: { 1: 'some_value' }, 'key3': ('one', 'two', 'three'), 'key4': { 1: 'uno', 'key4-2': 'dos' }, } config = Context(one_conf_dict) config.update(another_conf_dict) assert config == merged_conf_dicts
def test_that_external_module_contexts_are_imported_correctly( test_config_directory, ): application_config = { 'modules': { 'modules_directory': 'test_modules', 'enabled_modules': [{ 'name': 'module_with_context::*' }], }, } context = Context({ 'china': { 'capitol': 'beijing', }, }) module_manager = ModuleManager( config=application_config, context=context, ) expected_context = Context({ 'laos': { 'capitol': 'vientiane' }, 'china': { 'capitol': 'beijing' }, }) assert module_manager.application_context == expected_context
def test_items(self): config = Context() config['4'] = 'test' config['font'] = 'Comic Sans' config['5'] = '8' assert list(config.items()) == [ ('4', 'test'), ('font', 'Comic Sans'), ('5', '8'), ]
def test_importing_context_from_compiled_yml_file(): """YAML files should be importable into the context.""" context = Context({'section1': {'key_one': 'value_one'}}) assert context['section1']['key_one'] == 'value_one' test_config_file = Path(__file__).parent / 'test_config' / 'test.yml' context.import_context( from_path=test_config_file, from_section='section2', to_section='new_section', ) assert context['section1']['key_one'] == 'value_one' assert context['new_section']['var3'] == 'value1' context.import_context( from_path=test_config_file, from_section='section3', to_section='section3', ) assert context['section3']['env_variable'] == 'test_value, hello' context.import_context( from_path=test_config_file, from_section='section1', to_section='section1', ) assert context['section1']['var2'] == 'value1/value2' assert 'key_one' not in context['section1']
def user_configuration( config_directory: Optional[Path] = None, ) -> Tuple[ AstralityYAMLConfigDict, Dict[str, 'ModuleConfigDict'], Context, Path, ]: """ Return instantiation parameters for ModuleManager. :return: Tuple containing: astrality.yml dictionary, global modules dictionary, global context dictionary, and path to config directory. """ config_directory, config_file = infer_config_location(config_directory) # First get global context, which we can use when compiling other files context_file = config_directory / 'context.yml' if context_file.exists(): global_context = Context(utils.compile_yaml( path=context_file, context=Context(), )) else: global_context = Context() # Global configuration options config: AstralityYAMLConfigDict = utils.compile_yaml( # type: ignore path=config_file, context=global_context, ) # Insert default global settings that are not specified for section_name in ('astrality', 'modules',): section_content = config.get(section_name, {}) config[section_name] = ASTRALITY_DEFAULT_GLOBAL_SETTINGS[section_name].copy() # type: ignore # noqa config[section_name].update(section_content) # type: ignore # Globally defined modules modules_file = config_directory / 'modules.yml' if modules_file.exists(): modules = utils.compile_yaml( path=modules_file, context=global_context, ) else: modules = {} return config, modules, global_context, config_directory
def test_executing_several_action_blocks(test_config_directory, tmpdir): """Invoking execute() should execute all actions.""" temp_dir = Path(tmpdir) target = temp_dir / 'target.tmp' touched = temp_dir / 'touched.tmp' action_block_dict = { 'import_context': { 'from_path': 'context/mercedes.yml' }, 'compile': [{ 'content': 'templates/a_car.template', 'target': str(target), }], 'run': { 'shell': 'touch ' + str(touched) }, 'trigger': { 'block': 'on_startup' }, } context_store = Context() action_block = ActionBlock( action_block=action_block_dict, directory=test_config_directory, replacer=lambda x: x, context_store=context_store, ) action_block.execute(default_timeout=1) assert context_store == {'car': {'manufacturer': 'Mercedes'}} assert target.read_text() == 'My car is a Mercedes' assert touched.is_file()
def test_retrieving_triggers_from_action_block(): """All trigger instructions should be returned.""" action_block_dict = { 'trigger': [ { 'block': 'on_startup' }, { 'block': 'on_modified', 'path': 'test.template' }, ], } action_block = ActionBlock( action_block=action_block_dict, directory=Path('/'), replacer=lambda x: x, context_store=Context(), ) startup_trigger, on_modified_trigger = action_block.triggers() assert startup_trigger.block == 'on_startup' assert on_modified_trigger.block == 'on_modified' assert on_modified_trigger.specified_path == 'test.template' assert on_modified_trigger.relative_path == Path('test.template') assert on_modified_trigger.absolute_path == Path('/test.template')
def dummy_config(): """Return dummy configuration YAML file.""" test_conf = Path(__file__).parents[1] / 'test_config' / 'test.yml' return compile_yaml( path=test_conf, context=Context(), )
def test_accessing_existing_key(self): config = Context() config['some_key'] = 'some_value' assert config['some_key'] == 'some_value' config[-2] = 'some_other_value' assert config[-2] == 'some_other_value'
def test_executing_action_block_with_one_action( global_modules_config, test_config_directory, tmpdir, ): """Action block behaviour with only one action specified.""" temp_dir = Path(tmpdir) touched = temp_dir / 'touched.tmp' action_block_dict = { 'run': [{ 'shell': 'touch ' + str(touched) }], } action_block = ActionBlock( action_block=action_block_dict, directory=test_config_directory, replacer=lambda x: x, context_store=Context(), global_modules_config=global_modules_config, module_name='test', ) action_block.execute(default_timeout=1) assert touched.is_file()
def _action_block_factory( import_context={}, compile={}, copy={}, run={}, stow={}, symlink={}, directory=test_config_directory, replacer=lambda x: x, context_store=Context(), ): """Return module with given parameters.""" config = { 'import_context': import_context, 'compile': compile, 'copy': copy, 'run': run, 'stow': stow, 'symlink': symlink, } return ActionBlock( action_block=config, directory=directory, replacer=replacer, context_store=context_store, )
def test_that_replacer_is_run_every_time(context_directory): """ The replacer should be run a new every time self.execute() is invoked. """ context_import_dict = { 'from_path': 'several_sections.yml', 'from_section': 'section1', 'to_section': 'whatever', } context_store = Context() class Replacer: def __init__(self) -> None: self.invoke_number = 0 def __call__(self, option: str) -> str: self.invoke_number += 1 return option replacer = Replacer() import_context_action = ImportContextAction( options=context_import_dict, directory=context_directory, replacer=replacer, context_store=context_store, creation_store=CreatedFiles().wrapper_for(module='test'), ) import_context_action.execute() assert replacer.invoke_number == 3 import_context_action.execute() assert replacer.invoke_number == 6
def test_importing_entire_file(context_directory): """ Test importing all sections from context file. All context sections should be imported in the absence of `from_section`. """ context_import_dict = { 'from_path': 'several_sections.yml', } context_store = Context() import_context_action = ImportContextAction( options=context_import_dict, directory=context_directory, replacer=lambda x: x, context_store=context_store, creation_store=CreatedFiles().wrapper_for(module='test'), ) import_context_action.execute() expected_context = { 'section1': { 'k1_1': 'v1_1', 'k1_2': 'v1_2', }, 'section2': { 'k2_1': 'v2_1', 'k2_2': 'v2_2', }, } assert context_store == expected_context
def test_integer_index_resolution_without_earlier_index_key(self): config = Context() config['some_key'] = 'some_value' with pytest.raises(KeyError) as exception: config[2] assert exception.value.args[0] == \ 'Integer index "2" is non-existent and ' \ 'had no lower index to be substituted for'
def single_module_manager(simple_application_config, valid_module_section): return ModuleManager( config=simple_application_config, modules=valid_module_section, context=Context({ 'fonts': {1: 'FuraMono Nerd Font'}, }), )
def test_importing_all_context_sections_from_file( test_config_directory, action_block_factory, module_factory, module_manager_factory, ): context_file = test_config_directory / 'context' / 'several_sections.yml' original_context = Context({ 'section2': { 'k2_1': 'original_v2_1', 'k2_2': 'original_v2_2', }, 'section3': { 'k3_1': 'original_v3_1', 'k3_2': 'original_v3_2', }, }) import_context = action_block_factory( import_context={'from_path': str(context_file)}, ) module = module_factory(on_startup=import_context) module_manager = module_manager_factory(module, context=original_context) assert module_manager.application_context == original_context # We expect the following context after import expected_context = Context({ 'section1': { 'k1_1': 'v1_1', 'k1_2': 'v1_2', }, 'section2': { 'k2_1': 'v2_1', 'k2_2': 'v2_2', }, 'section3': { 'k3_1': 'original_v3_1', 'k3_2': 'original_v3_2', }, }) # Now run startup actions, resulting in the file being imported module_manager.finish_tasks() # Assert that the new application_context is as expected assert module_manager.application_context == expected_context
def test_null_object_pattern(): """Test initializing action with no behaviour.""" import_context_action = ImportContextAction( options={}, directory=Path('/'), replacer=lambda x: x, context_store=Context(), ) import_context_action.execute()
def test_recompile_templates_when_modified_overridden( three_watchable_files, test_config_directory, ): """ If a file is watched in a on_modified block, it should override the reprocess_modified_files option. """ template, target, touch_target = three_watchable_files template.touch() modules = { 'module_name': { 'on_startup': { 'compile': { 'content': str(template), 'target': str(target), }, }, 'on_modified': { str(template): { 'run': {'shell': 'touch ' + str(touch_target)}, }, }, }, } application_config = {'modules': {'reprocess_modified_files': True}} module_manager = ModuleManager( config=application_config, modules=modules, context=Context({ 'section': {1: 'value'}, }), directory=test_config_directory, ) # Sanity check before beginning testing with open(template) as file: assert file.read() == '' assert not target.is_file() # Compile the template module_manager.finish_tasks() with open(target) as file: assert file.read() == '' # Now write to the template and see if it is *compiled*, but the on_modified # command is run instead template.write_text('{{ section.2 }}') retry = Retry() assert retry(lambda: target.read_text() == '') assert retry(lambda: touch_target.is_file()) module_manager.exit()
def context(self, context: Context = Context()) -> Context: """ Return context defined in module source. :param context: Context used when compiling "context.yml". :return: Context dictionary. """ if not self.context_file.exists(): return Context() if hasattr(self, '_context'): return self._context self._context = Context(utils.compile_yaml( path=self.context_file, context=context, )) return self._context
def test_null_object_pattern(): """An empty action block should have no behaviour.""" action_block = ActionBlock( action_block={}, directory=Path('/'), replacer=lambda x: x, context_store=Context(), ) action_block.execute(default_timeout=1)
def test_initialization_of_config_class_with_dict(self): conf_dict = { 'key1': 'value1', 'key2': 'value2', 'key3': ('one', 'two', 'three'), 'key4': {'key4-1': 'uno', 'key4-2': 'dos'}, } config = Context(conf_dict) assert config == conf_dict
def test_retrieving_triggers_from_action_block_without_triggers(): """Action block with no triggers should return empty tuple.""" action_block = ActionBlock( action_block={}, directory=Path('/'), replacer=lambda x: x, context_store=Context(), ) assert action_block.triggers() == tuple()
def test_null_object_pattern(): """Test initializing action with no behaviour.""" import_context_action = ImportContextAction( options={}, directory=Path('/'), replacer=lambda x: x, context_store=Context(), creation_store=CreatedFiles().wrapper_for(module='test'), ) import_context_action.execute()
def test_context_class(self): context = Context() context[1] = 'firs_value' context[2] = 'second_value' context['string_key'] = 'string_value' assert context[1] == 'firs_value' assert context[2] == 'second_value' assert context[3] == 'second_value' assert context['string_key'] == 'string_value'
def test_null_object_pattern(global_modules_config): """An empty action block should have no behaviour.""" action_block = ActionBlock( action_block={}, directory=Path('/'), replacer=lambda x: x, context_store=Context(), global_modules_config=global_modules_config, module_name='test', ) action_block.execute(default_timeout=1)
def test_instantiating_context_object_with_path(): """Paths should be read into the context object.""" test_context = Path(__file__).parent / 'test_config' / 'test.yml' context = Context(test_context) assert context == Context({ 'section1': { 'var1': 'value1', 'var2': 'value1/value2', }, 'section2': { 'var3': 'value1', 'empty_string_var': '', }, 'section3': { 'env_variable': 'test_value, hello', }, 'section4': { 1: 'primary_value', }, })
def module( valid_module_section, test_config_directory, ): return Module( name='test_module', module_config=valid_module_section['test_module'], module_directory=test_config_directory, context_store=Context({ 'fonts': {1: 'FuraMono Nerd Font'}, }), )
def test_getter(self): config = Context() assert config.get('from_empty_config') is None config['test'] = 'something' assert config.get('test') == 'something' assert config.get('test', '4') == 'something' assert config.get('non_existent_key') is None assert config.get('non_existent_key', '4') == '4'
def test_updating_context_with_context(self): context1 = Context({'key1': 1}) context2 = Context({'key2': 2}) context1.update(context2) expected_result = Context({'key1': 1, 'key2': 2}) assert context1 == expected_result
def test_compilation_of_template( self, valid_module_section, simple_application_config, module, conf, caplog, ): valid_module_section[ 'test_module' ][ 'event_listener' ][ 'type' ] = 'solar' compiled_template_content = 'some text\n' + os.environ['USER'] \ + '\nFuraMono Nerd Font' module_manager = ModuleManager( config=simple_application_config, modules=valid_module_section, context=Context({ 'fonts': {1: 'FuraMono Nerd Font'}, }), ) directory = module_manager.config_directory caplog.clear() module_manager.execute(action='compile', block='on_startup') template_file = str( (directory / '../templates/test_template.conf').resolve(), ) compiled_template = str( list( module_manager.modules['test_module'] .performed_compilations()[Path(template_file)], )[0], ) with open('/tmp/compiled_result', 'r') as file: compiled_result = file.read() assert compiled_template_content == compiled_result assert caplog.record_tuples == [ ( 'astrality.compiler', logging.INFO, f'[Compiling] Template: "{template_file}" ' f'-> Target: "{compiled_template}"', ), ]