def test_all_three_actions_in_on_modified_block( three_watchable_files, test_config_directory, ): file1, file2, file3 = three_watchable_files car_template = test_config_directory / 'templates' / 'a_car.template' mercedes_context = test_config_directory / 'context' / 'mercedes.yml' tesla_context = test_config_directory / 'context' / 'tesla.yml' modules = { 'car': { 'on_startup': { 'import_context': { 'from_path': str(mercedes_context), }, 'compile': { 'content': str(car_template), 'target': str(file1), }, }, 'on_modified': { str(file2): { 'import_context': { 'from_path': str(tesla_context), }, 'compile': { 'content': str(car_template), 'target': str(file1), }, 'run': { 'shell': 'touch ' + str(file3) }, }, }, }, } module_manager = ModuleManager(modules=modules) # Sanity check before beginning testing assert not file1.is_file() assert not file2.is_file() assert not file3.is_file() # Now finish tasks, i.e. on_startup block module_manager.finish_tasks() assert file1.is_file() assert not file2.is_file() assert not file3.is_file() # Check that the correct context is inserted with open(file1) as file: assert file.read() == 'My car is a Mercedes' # Now modify file2 such that the on_modified block is triggered file2.write_text('some new content') # The on_modified run command should now have been executed assert Retry()(lambda: file3.is_file()) module_manager.exit()
def test_direct_invocation_of_modifed_method_of_module_manager(modules_config): ( modules, empty_template, empty_template_target, touch_target, secondary_template, secondary_template_target, ) = modules_config module_manager = ModuleManager(modules=modules) # PS: Disabling the directory watcher is not necessary, as it is done in # the startup method. # Now write new text to the template empty_template.write_text('new content') # And trigger the modified method manually module_manager.file_system_modified(empty_template) # And assert that the new template has been compiled assert empty_template_target.is_file() with open(empty_template_target) as file: assert file.read() == 'new content' # And that the new file has been touched assert Retry()(lambda: touch_target.is_file())
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 test_hot_reloading( test_template_targets, test_config_directory, ): template_target1, template_target2 = test_template_targets config1 = test_config_directory / 'modules1.yml' config2 = test_config_directory / 'modules2.yml' target_config = test_config_directory / 'modules.yml' # Copy the first configuration into place shutil.copy(str(config1), str(target_config)) modules1 = utils.compile_yaml( config1, context={}, ) application_config1 = {'astrality': {'hot_reload_config': True}} module_manager = ModuleManager( config=application_config1, modules=modules1, directory=test_config_directory, ) # Before beginning, the template should not be compiled assert not template_target1.is_file() # But when we finalize tasks, it should be compiled module_manager.finish_tasks() assert template_target1.is_file() # Also check that the filewatcher has been started assert module_manager.directory_watcher.observer.is_alive() # We now "edit" the configuration file shutil.copy(str(config2), str(target_config)) # Since hot reloading is enabled, the new template target should be # compiled, and the old one cleaned up retry = Retry() assert retry(lambda: template_target2.is_file()) assert retry(lambda: not template_target1.is_file()) # And we switch back again shutil.copy(str(config1), str(target_config)) assert retry(lambda: template_target1.is_file()) assert retry(lambda: not template_target2.is_file()) # Cleanup config file if target_config.is_file(): os.remove(target_config) # Stop the filewatcher module_manager.directory_watcher.stop()
def test_filesystem_watcher(watch_dir): """ Test correct callback invocation on directory watching. Sometimes the on_modified function is called several times by watchdog, for a unknown reason. It might be other tests which interfer. We therefore check if the lower bound of calls is satisfied, but do not test the exact number of calls to on_modified. """ ( watched_directory, recursive_dir, test_file1, test_file2, dir_watcher, event_saver, ) = watch_dir # Start watching the directory dir_watcher.start() # Nothing has been modified yet assert event_saver.argument is None assert event_saver.called == 0 # Create an empty file test_file1.touch() # We might have to try several times, as filewatching can be slow retry = Retry() # New files are not considered "modified" assert event_saver.argument is None assert event_saver.called == 0 # But when we write to it, it is considered "modified" test_file1.write_text('test_content') assert retry(lambda: event_saver.argument == test_file1) assert event_saver.called >= 1 # Create a directory in the watched directory recursive_dir.mkdir(parents=True) # Subdirectories are not of interest assert retry(lambda: event_saver.argument == test_file1) assert retry(lambda: event_saver.called >= 1) # Create a file in the subdirectory test_file2.write_text('test') # Both the touch event and the write event are considered of interest assert retry(lambda: event_saver.argument == test_file2) assert event_saver.called == 2
def test_correct_relative_paths_used_in_external_module( temp_test_files, test_config_directory, ): application_config = { 'modules': { 'modules_directory': 'test_modules', 'enabled_modules': [{ 'name': 'using_all_actions::*' }], }, } module_manager = ModuleManager(config=application_config) ( compile_target, touch_target, watch_touch_target, watched_file, ) = temp_test_files # Sanity check before testing for file in ( compile_target, touch_target, watch_touch_target, ): assert not file.is_file() # Finish task and see if context import, compilation, and run has been # correctly run relative to the module directory path module_manager.finish_tasks() with open(compile_target, 'r') as file: assert file.read() == "Vietnam's capitol is Ho Chi Minh City" assert touch_target.is_file() # Now modify the observed file, and see if on_modified is triggered watched_file.write_text('This watched file has been modified') retry = Retry() assert retry( lambda: compile_target.read_text() == "Vietnam's capitol is Hanoi", ) assert retry(lambda: watch_touch_target.is_file()) touch_target.unlink() compile_target.unlink() watch_touch_target.unlink() watched_file.write_text('')
def test_that_no_reprocess_modified_files_does_not_cause_keep_running(): """ModuleManager without reprocess_modified_files does not keep_running.""" module_manager = ModuleManager( config={ 'modules': { 'reprocess_modified_files': False, 'enabled_modules': [{ 'name': 'A' }], }, }, modules={'A': {}}, ) # We have to retry here, as processes from earlier tests might interfer assert Retry()(lambda: module_manager.keep_running is False)
def test_killing_old_running_process(self): """The same running process should be killed.""" perpetual_process = psutil.Popen([ 'python', '-c', '"from time import sleep; sleep(9999999999999)"', ]) pidfile = XDG().data('astrality.pid') utils.dump_yaml( data=perpetual_process.as_dict( attrs=['pid', 'create_time', 'username'], ), path=pidfile, ) kill_old_astrality_processes() assert Retry()(lambda: not perpetual_process.is_running())
def test_that_stowed_templates_are_also_watched(three_watchable_files): """Stowing template instead of compiling it should still be watched.""" template, target, _ = three_watchable_files template.touch() modules = { 'module_name': { 'on_startup': { 'stow': { 'content': str(template), 'target': str(target), 'templates': '(.+)', 'non_templates': 'ignore', }, }, }, } application_config = {'modules': {'reprocess_modified_files': True}} module_manager = ModuleManager( config=application_config, modules=modules, context=Context({ 'section': { 1: 'value' }, }), ) # Sanity check before beginning testing with open(template) as file: assert file.read() == '' assert not target.is_file() # Stow the template module_manager.finish_tasks() with open(target) as file: assert file.read() == '' # Now write to the template and see if it is recompiled template.write_text('{{ section.2 }}') assert Retry()(lambda: target.read_text() == 'value') module_manager.exit()
def test_recompile_templates_when_modified(three_watchable_files): template, target, _ = three_watchable_files template.touch() modules = { 'module_name': { 'on_startup': { 'compile': { 'content': str(template), 'target': str(target), }, }, }, } application_config = {'modules': {'reprocess_modified_files': True}} module_manager = ModuleManager( config=application_config, modules=modules, context=Context({ 'section': { 1: 'value' }, }), ) # 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 recompiled template.write_text('{{ section.2 }}') assert Retry()(lambda: target.read_text() == 'value') module_manager.exit() module_manager.directory_watcher.stop()
def test_not_killing_new_procces_with_same_pid(self): """The process should not be killed when it is not the original saved""" perpetual_process = psutil.Popen([ 'python', '-c', '"from time import sleep; sleep(9999999999999)"', ]) process_data = perpetual_process.as_dict( attrs=['pid', 'create_time', 'username'], ) process_data['create_time'] += 1 utils.dump_yaml( data=process_data, path=XDG().data('astrality.pid'), ) kill_old_astrality_processes() assert Retry()(lambda: perpetual_process.is_running()) perpetual_process.kill()
def test_on_modified_event_in_module(modules_config): ( modules, empty_template, empty_template_target, touch_target, secondary_template, secondary_template_target, ) = modules_config module_manager = ModuleManager(modules=modules) # Start the file watcher by invoking the startup command indirectly # through finish_tasks() method module_manager.finish_tasks() # Assert that the template file is really empty as a sanity check assert empty_template.read_text() == '' # And that target files are not created yet assert not touch_target.is_file() assert not empty_template_target.is_file() assert not secondary_template_target.is_file() # Trigger the on_modified event empty_template.write_text('new content') # And assert that the new template has been compiled retry = Retry() assert retry(lambda: empty_template_target.is_file()) assert retry(lambda: empty_template_target.read_text() == 'new content') # Assert that also templates from other modules are compiled assert retry(lambda: secondary_template_target.is_file()) assert retry( lambda: secondary_template_target.read_text() == 'one\ntwo\nthree', ) # And that the new file has been touched assert retry(lambda: touch_target.is_file())
def test_that_only_startup_event_block_is_run_on_startup( two_test_file_paths, test_config_directory, freezer, ): thursday = datetime( year=2018, month=2, day=15, hour=12, ) freezer.move_to(thursday) test_file1, test_file2 = two_test_file_paths modules = { 'A': { 'event_listener': {'type': 'weekday'}, 'on_startup': { 'run': [{'shell': 'touch ' + str(test_file1)}], }, 'on_event': { 'run': [{'shell': 'touch ' + str(test_file2)}], }, }, } module_manager = ModuleManager( modules=modules, ) # Before call to finish_tasks, no actions should have been performed assert not test_file1.is_file() and not test_file2.is_file() # Now call finish_tasks for the first time, only startup event block should # be run module_manager.finish_tasks() retry = Retry() assert retry(lambda: test_file1.is_file()) assert retry(lambda: not test_file2.is_file())
def test_importing_context_on_modification( three_watchable_files, test_config_directory, ): """Test that context values are imported in on_modified blocks.""" file1, *_ = three_watchable_files mercedes_context = test_config_directory / 'context' / 'mercedes.yml' modules = { 'module_name': { 'on_modified': { str(file1): { 'import_context': { 'from_path': str(mercedes_context), }, }, }, }, } module_manager = ModuleManager( modules=modules, context=Context({ 'car': { 'manufacturer': 'Tesla' }, }), ) module_manager.finish_tasks() # Sanity check before modifying file1 assert module_manager.application_context['car']['manufacturer'] == 'Tesla' # After modifying file1, Mercedes should have been imported file1.touch() file1.write_text('new content, resulting in importing Mercedes') assert Retry()(lambda: module_manager.application_context['car'][ 'manufacturer'] == 'Mercedes', )