def test_shell_command_timeout():
    """Shell commands can time out."""
    default_times_out = Requirement(
        requirements={'shell': 'sleep 0.1'},
        directory=Path('/'),
        timeout=0.01,
    )
    assert not default_times_out

    default_does_not_timeout = Requirement(
        requirements={'shell': 'sleep 0.1'},
        directory=Path('/'),
        timeout=0.2,
    )
    assert default_does_not_timeout

    specifed_does_not_timeout = Requirement(
        requirements={
            'shell': 'sleep 0.1',
            'timeout': 0.2
        },
        directory=Path('/'),
        timeout=0.5,
    )
    assert specifed_does_not_timeout

    specified_does_timeout = Requirement(
        requirements={
            'shell': 'sleep 0.1',
            'timeout': 0.05
        },
        directory=Path('/'),
        timeout=1000,
    )
    assert not specified_does_timeout
def test_installed_requirement():
    """Requirement should be truthy when value is in $PATH."""
    successful_installed_requirement = Requirement(
        requirements={'installed': 'ls'},
        directory=Path('/'),
    )
    assert successful_installed_requirement

    unsuccessful_installed_requirement = Requirement(
        requirements={'installed': 'does_not_exist'},
        directory=Path('/'),
    )
    assert not unsuccessful_installed_requirement
def test_environment_variable_requirement():
    """Requirement should be truthy when environment variable is available."""
    successful_env_requirement = Requirement(
        requirements={'env': 'EXAMPLE_ENV_VARIABLE'},
        directory=Path('/'),
    )
    assert successful_env_requirement

    unsuccessful_env_requirement = Requirement(
        requirements={'env': 'THIS_IS_NOT_SET'},
        directory=Path('/'),
    )
    assert not unsuccessful_env_requirement
def test_that_shell_commands_are_run_in_correct_diretory():
    """All shell commands should be run from 'directory'"""
    successful_shell_requirement = Requirement(
        requirements={'shell': 'ls tmp'},
        directory=Path('/'),
    )
    assert successful_shell_requirement

    unsuccessful_shell_requirement = Requirement(
        requirements={'shell': 'ls does_not_exist'},
        directory=Path('/'),
    )
    assert not unsuccessful_shell_requirement
def test_shell_command_requirement():
    """Requirement should be truthy when command returns 0 exit code."""
    successful_shell_requirement = Requirement(
        requirements={'shell': 'command -v ls'},
        directory=Path('/'),
    )
    assert successful_shell_requirement

    unsuccessful_shell_requirement = Requirement(
        requirements={'shell': 'command -v does_not_exist'},
        directory=Path('/'),
    )
    assert not unsuccessful_shell_requirement
def test_null_object_pattern():
    """Empty requirements should be considered satisfied."""
    successful_shell_requirement = Requirement(
        requirements={},
        directory=Path('/'),
    )
    assert successful_shell_requirement
Example #7
0
def test_recursive_module_requirements():
    """Missing dependencies should propagate."""
    moduleA = Module(
        name='A',
        module_config={'requires': [{
            'module': 'B'
        }]},
        module_directory=Path(__file__).parent,
    )
    moduleB = Module(
        name='B',
        module_config={'requires': [{
            'module': 'C'
        }]},
        module_directory=Path(__file__).parent,
    )
    moduleC = Module(
        name='C',
        module_config={'requires': [{
            'module': 'D'
        }]},
        module_directory=Path(__file__).parent,
    )

    assert Requirement.pop_missing_module_dependencies(modules={
        'A': moduleA,
        'B': moduleB,
        'C': moduleC
    }, ) == {}
Example #8
0
    def valid_module(
        name: str,
        config: ModuleConfigDict,
        requires_timeout: Union[int, float],
        requires_working_directory: Path,
    ) -> bool:
        """
        Check if the given dict represents a valid enabled module.

        The method determines this by inspecting any requires items the module
        might have specified.

        :param name: Name of module, used for logging purposes.
        :param config: Configuration dictionary of the module.
        :param requires_timeout: Time to wait for shell command requirements.
        :param requires_working_directory: CWD for shell commands.
        :return: True if module should be enabled.
        """
        if not config.get('enabled', True):
            return False

        # The module is enabled, now check if all requirements are satisfied
        requires: List[RequirementDict] = cast_to_list(
            config.get(
                'requires',
                {},
            ),
        )
        requirements = [
            Requirement(
                requirements=requirements_dict,
                directory=requires_working_directory,
                timeout=requires_timeout,
            )
            for requirements_dict
            in requires
        ]
        if all(requirements):
            return True
        else:
            logger.warning(
                f'[module/{name}] ' +
                ', '.join([
                    repr(requirement)
                    for requirement
                    in requirements
                ]) + '!',
            )
            return False
Example #9
0
    def valid_class_section(
        section: ModuleConfig,
        requires_timeout: Union[int, float],
        requires_working_directory: Path,
    ) -> bool:
        """Check if the given dict represents a valid enabled module."""
        if not len(section) == 1:
            raise RuntimeError(
                'Tried to check module section with dict '
                'which does not have exactly one item.', )

        try:
            module_name = next(iter(section.keys()))
            valid_module_name = \
                module_name.split('/')[0].lower() == 'module'  # type: ignore
            enabled = section[module_name].get('enabled', True)
            if not (valid_module_name and enabled):
                return False

        except KeyError:
            return False

        # The module is enabled, now check if all requirements are satisfied
        requires: List[RequirementDict] = cast_to_list(
            section[module_name].get(
                'requires',
                {},
            ), )
        requirements = [
            Requirement(
                requirements=requirements_dict,
                directory=requires_working_directory,
                timeout=requires_timeout,
            ) for requirements_dict in requires
        ]
        if all(requirements):
            return True
        else:
            logger.warning(
                f'[{module_name}] ' +
                ", ".join([repr(requirement)
                           for requirement in requirements]) + '!', )
            return False
Example #10
0
def test_requiring_a_global_module():
    """You should be able to require global modules."""
    moduleA = Module(
        name='A',
        module_config={'requires': [{
            'module': 'B'
        }]},
        module_directory=Path(__file__).parent,
    )
    assert moduleA.depends_on == ('B', )

    moduleB = Module(
        name='B',
        module_config={'run': {
            'shell': 'echo hi!'
        }},
        module_directory=Path(__file__).parent,
    )
    assert moduleB.depends_on == tuple()

    moduleC = Module(
        name='C',
        module_config={'requires': [{
            'module': 'D'
        }]},
        module_directory=Path(__file__).parent,
    )
    assert moduleC.depends_on == ('D', )

    assert Requirement.pop_missing_module_dependencies(modules={
        'A': moduleA,
        'B': moduleB,
        'C': moduleC
    }, ) == {
        'A': moduleA,
        'B': moduleB
    }
Example #11
0
    def __init__(
        self,
        config: AstralityYAMLConfigDict = {},
        modules: Dict[str, ModuleConfigDict] = {},
        context: Context = Context(),
        directory: Path = Path(__file__).parent / 'tests' / 'test_config',
        dry_run: bool = False,
    ) -> None:
        """Initialize a ModuleManager object from `astrality.yml` dict."""
        self.config_directory = directory
        self.application_config = config
        self.application_context = context
        self.dry_run = dry_run

        self.startup_done = False
        self.last_module_events: Dict[str, str] = {}

        # Get module configurations which are externally defined
        self.global_modules_config = GlobalModulesConfig(
            config=config.get('modules', {}),
            config_directory=self.config_directory,
        )
        self.reprocess_modified_files = \
            self.global_modules_config.reprocess_modified_files

        self.modules: Dict[str, Module] = {}

        # Insert externally managed modules
        for external_module_source \
                in self.global_modules_config.external_module_sources:
            # Insert context defined in external configuration
            module_context = external_module_source.context(
                context=self.application_context,
            )
            self.application_context.reverse_update(module_context)

            module_configs = external_module_source.modules(
                context=self.application_context,
            )
            module_directory = external_module_source.directory

            for module_name, module_config in module_configs.items():
                if module_name \
                        not in self.global_modules_config.enabled_modules:
                    continue

                if not Module.valid_module(
                    name=module_name,
                    config=module_config,
                    requires_timeout=self.global_modules_config.
                    requires_timeout,  # noqa
                    requires_working_directory=module_directory,
                ):
                    continue

                module = Module(
                    name=module_name,
                    module_config=module_config,
                    module_directory=module_directory,
                    replacer=self.interpolate_string,
                    context_store=self.application_context,
                    global_modules_config=self.global_modules_config,
                    dry_run=dry_run,
                )
                self.modules[module.name] = module

        # Insert modules defined in `astrality.yml`
        for module_name, module_config in modules.items():
            # Check if this module should be included
            if module_name not in self.global_modules_config.enabled_modules:
                continue

            if not Module.valid_module(
                name=module_name,
                config=module_config,
                requires_timeout=self.global_modules_config.requires_timeout,
                requires_working_directory=self.config_directory,
            ):
                continue

            module = Module(
                name=module_name,
                module_config=module_config,
                module_directory=self.config_directory,
                replacer=self.interpolate_string,
                context_store=self.application_context,
                global_modules_config=self.global_modules_config,
                dry_run=dry_run,
            )
            self.modules[module.name] = module

        # Remove modules which depends on other missing modules
        Requirement.pop_missing_module_dependencies(self.modules)

        # Initialize the config directory watcher, but don't start it yet
        self.directory_watcher = DirectoryWatcher(
            directory=self.config_directory,
            on_modified=self.file_system_modified,
        )

        logger.info('Enabled modules: ' + ', '.join(self.modules.keys()))