Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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()