def test_has_unfinished_tasks(simple_application_config, freezer): # Move time to midday midday = datetime.now().replace(hour=12, minute=0) freezer.move_to(midday) # At instanziation, the module should have unfinished tasks weekday_module = ModuleManager(simple_application_config) assert weekday_module.has_unfinished_tasks() == True # After finishing tasks, there should be no unfinished tasks (duh!) weekday_module.finish_tasks() assert weekday_module.has_unfinished_tasks() == False # If we move the time forwards, but not to a new event, there should still # not be any unfinished tasks before_midnight = datetime.now().replace(hour=23, minute=59) freezer.move_to(before_midnight) assert weekday_module.has_unfinished_tasks() == False # But right after a event (new weekday), there should be unfinished # tasks two_minutes = timedelta(minutes=2) freezer.move_to(before_midnight + two_minutes) assert weekday_module.has_unfinished_tasks() == True # Again, after finishing tasks, there should be no unfinished tasks left weekday_module.finish_tasks() assert weekday_module.has_unfinished_tasks() == False
def test_detection_of_new_event_involving_several_modules( config_with_modules, freezer, ): modules, context = config_with_modules # Move time to right before noon solar_event_listener = event_listener.Solar(modules) noon = solar_event_listener.location.sun()['noon'] one_minute = timedelta(minutes=1) freezer.move_to(noon - one_minute) module_manager = ModuleManager( modules=modules, context=context, ) # All modules should now considered to have now events assert module_manager.has_unfinished_tasks() is True # Running on event method for all the event changed modules module_manager.finish_tasks() # After running these methods, they should all be reverted to not changed assert module_manager.has_unfinished_tasks() is False # Move time to right after noon freezer.move_to(noon + one_minute) # The solar event listener should now be considered to have been event # changed assert module_manager.has_unfinished_tasks() is True # Again, check if on_event() method makes them unchanged module_manager.finish_tasks() assert module_manager.has_unfinished_tasks() is False # Move time two days forwards two_days = timedelta(days=2) freezer.move_to(noon + two_days) # Now both event listeners should be considered to have new events assert module_manager.has_unfinished_tasks() is True
def main( modules: List[str] = [], logging_level: str = 'INFO', dry_run: bool = False, test: bool = False, ): """ Run the main process for Astrality. :param modules: Modules to be enabled. If empty, use astrality.yml. :param logging_level: Loging level. :param dry_run: If file system actions should be printed and skipped. :param test: If True, return after one iteration loop. """ if 'ASTRALITY_LOGGING_LEVEL' in os.environ: # Override logging level if env variable is set logging_level = os.environ['ASTRALITY_LOGGING_LEVEL'] # Set the logging level to the configured setting logging.basicConfig( level=logging.getLevelName(logging_level), # type: ignore ) if not modules and not dry_run and not test: # Quit old astrality instances kill_old_astrality_processes() # How to quit this process def exit_handler(signal=None, frame=None) -> None: """ Cleanup all temporary files and run module exit handlers. The temp directory is left alone, for two reasons: 1: An empty directory uses neglible disk space. 2: If this process is interrupted by another Astrality instance, we might experience race conditions when the exit handler deletes the temporary directory *after* the new Astrality instance creates it. """ logger.critical('Astrality was interrupted') logger.info('Cleaning up temporary files before exiting...') try: # Run all the module exit handlers module_manager.exit() except NameError: # The module_manager instance has not been assigned yet. pass try: sys.exit(0) except SystemExit: os._exit(0) # Some SIGINT signals are not properly interupted by python and converted # into KeyboardInterrupts, so we have to register a signal handler to # safeguard against such cases. This seems to be the case when conky is # launched as a subprocess, making it the process that receives the SIGINT # signal and not python. These signal handlers cause issues for \ # NamedTemporaryFile.close() though, so they are only registrered when # we are not testing. if not test: signal.signal(signal.SIGINT, exit_handler) # Also catch kill-signkal from OS, # e.g. `kill $(pgrep -f "python astrality.py")` signal.signal(signal.SIGTERM, exit_handler) try: ( config, module_configs, global_context, directory, ) = user_configuration() if modules: config['modules']['enabled_modules'] = [{ 'name': module_name } for module_name in modules] # Delay further actions if configuration says so time.sleep(config['astrality']['startup_delay']) module_manager = ModuleManager( config=config, modules=module_configs, context=global_context, directory=directory, dry_run=dry_run, ) module_manager.finish_tasks() while True: if module_manager.has_unfinished_tasks(): # TODO: Log which new event which has been detected logger.info('New event detected.') module_manager.finish_tasks() logger.info(f'Event change routine finished.') if test or dry_run: logger.debug('Main loop interupted due to --dry-run.') return elif not module_manager.keep_running: logger.info( 'No more tasks to be performed. ' 'Executing on_exit blocks.', ) module_manager.exit() return else: logger.info( f'Waiting {module_manager.time_until_next_event()} ' 'until next event change and ensuing update.', ) # Weird bug related to sleeping more than 10e7 seconds # on MacOS, causing OSError: Invalid Argument wait = module_manager.time_until_next_event().total_seconds() if wait >= 10e7: wait = 10e7 time.sleep(wait) except KeyboardInterrupt: # pragma: no cover exit_handler()