From a36aa05baeb11267d9240d006c122351778c742c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B2=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=94=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=81?= Date: Mon, 28 Sep 2020 12:07:03 +0300 Subject: [PATCH] Implemented commands and scripts modules and parameter's container too. --- .../old_vars/os => commands}/__init__.py | 0 calculate/commands/commands.py | 175 ++ .../scripts}/__init__.py | 0 calculate/scripts/scripts.py | 986 +++++++++ calculate/templates/template_engine.py | 30 +- calculate/templates/template_processor.py | 203 +- calculate/utils/files.py | 2 +- calculate/utils/io_module.py | 5 + calculate/utils/package.py | 19 + calculate/variables/datavars.py | 209 +- calculate/variables/loader.py | 78 +- calculate/variables/old_vars/datavars.py | 575 ----- calculate/variables/old_vars/main/__init__.py | 5 - .../variables/old_vars/os/gentoo/__init__.py | 88 - calculate/variables/old_vars/vars_loader.py | 335 --- calculate/variables/parameters.py | 727 +++++++ calculate/vars/main/__init__.py | 19 +- calculate/vars/os/gentoo/__init__.py | 5 +- calculate/vars/system/__init__.py | 2 +- pytest.ini | 8 +- run.py | 164 +- run_templates.py | 38 + tests/commands/__init__.py | 0 tests/commands/test_commands.py | 194 ++ tests/commands/testfiles/calculate.ini | 0 .../testfiles/gentoo.backup/ini_vars_0.backup | 0 .../gentoo.backup/portage/calculate.ini | 0 .../portage/profiles/calculate.ini | 0 .../portage/profiles/main/calculate.ini | 0 .../repos/calculate/profiles/calculate.ini | 0 .../profiles/default/20/calculate.ini | 0 .../profiles/default/amd64/20/calculate.ini | 0 .../default/amd64/20/desktop/calculate.ini | 0 .../profiles/default/amd64/20/desktop/parent | 3 + .../profiles/default/amd64/20/parent | 2 + .../profiles/default/amd64/calculate.ini | 0 .../calculate/profiles/default/amd64/parent | 1 + .../calculate/profiles/default/calculate.ini | 0 .../profiles/default/desktop/calculate.ini | 0 .../repos/calculate/profiles/default/parent | 1 + .../distros/profiles/CLD/amd64/calculate.ini | 2 + .../repos/distros/profiles/CLD/amd64/parent | 2 + .../repos/distros/profiles/CLD/calculate.ini | 21 + .../repos/distros/profiles/CLD/parent | 1 + .../distros/profiles/CLDX/amd64/calculate.ini | 0 .../repos/distros/profiles/CLDX/amd64/parent | 2 + .../repos/distros/profiles/CLDX/calculate.ini | 0 .../repos/distros/profiles/CLDX/parent | 1 + .../repos/distros/profiles/calculate.ini | 0 .../testfiles/variables/main/__init__.py | 4 + .../testfiles/variables/os/__init__.py | 66 + .../testfiles/variables/os/gentoo/__init__.py | 57 + .../testfiles/variables/system/__init__.py | 18 + tests/scripts/__init__.py | 0 tests/scripts/test_scripts.py | 1854 +++++++++++++++++ tests/templates/test_directory_processor.py | 48 + tests/templates/test_template_engine.py | 49 +- tests/templates/test_template_executor.py | 11 +- .../templates_32/install/.calculate_directory | 3 + .../install/dir_32/.calculate_directory | 1 + .../templates_32/install/dir_32/file_0 | 5 + .../templates_32/update/.calculate_directory | 3 + .../update/dir_33/.calculate_directory | 1 + .../templates_32/update/dir_33/file_0 | 5 + .../templates_33/install/.calculate_directory | 2 + .../install/dir_34/.calculate_directory | 1 + .../templates_33/install/dir_34/file_0 | 1 + .../templates_33/update/.calculate_directory | 3 + .../update/dir_35/.calculate_directory | 1 + .../templates_33/update/dir_35/file_0 | 5 + .../templates_34/install/.calculate_directory | 2 + .../install/dir_36/.calculate_directory | 2 + .../templates_34/install/dir_36/file_0 | 1 + .../test_runner/install/.calculate_directory | 2 + .../install/dir_2/.calculate_directory | 1 + .../test_runner/install/dir_2/file_0 | 5 + .../install/dir_3/.calculate_directory | 1 + .../test_runner/install/dir_3/file_0 | 1 + .../test_runner/remove/.calculate_directory | 2 + .../remove/test_dir/.calculate_directory | 1 + .../test_runner/update/.calculate_directory | 2 + .../update/dir_1/.calculate_directory | 1 + .../testfiles/test_runner/update/dir_1/file_0 | 4 + .../update/dir_2/.calculate_directory | 1 + .../testfiles/test_runner/update/dir_2/file_0 | 4 + tests/utils/test_files.py | 4 +- tests/variables/old_vars/old_vars.py | 793 ------- .../old_vars/variables/level/__init__.py | 119 -- .../variables/level/level2/__init__.py | 6 - .../old_vars/variables/main/__init__.py | 6 - .../old_vars/variables/os/__init__.py | 65 - tests/variables/test_calculateini.py | 5 - .../{test_variables.py => test_datavars.py} | 270 ++- tests/variables/test_parameters.py | 1090 ++++++++++ .../repos/distros/profiles/CLD/calculate.ini | 4 +- .../testfiles/parameters_ini/calculate.ini | 0 .../variables_1/level/level_2/__init__.py | 7 +- .../testfiles/variables_13/main/__init__.py | 4 + .../testfiles/variables_13/os/__init__.py | 50 + .../variables_13/os/gentoo/__init__.py | 57 + .../testfiles/variables_13/system/__init__.py | 19 + .../testfiles/variables_14/level/__init__.py | 62 + .../variables_14/level/level_2/__init__.py | 7 + .../testfiles/variables_14/main/__init__.py | 4 + .../testfiles/variables_14/os/__init__.py | 38 + .../testfiles/variables_15/main/__init__.py | 4 + .../testfiles/variables_15/os/__init__.py | 50 + .../variables_15/os/gentoo/__init__.py | 57 + .../testfiles/variables_15/system/__init__.py | 19 + .../testfiles/variables_16/main/__init__.py | 4 + .../testfiles/variables_16/os/__init__.py | 50 + .../variables_16/os/gentoo/__init__.py | 57 + .../testfiles/variables_16/system/__init__.py | 19 + .../testfiles/variables_17/main/__init__.py | 4 + .../testfiles/variables_17/os/__init__.py | 66 + .../variables_17/os/gentoo/__init__.py | 57 + .../testfiles/variables_17/system/__init__.py | 19 + 117 files changed, 6809 insertions(+), 2278 deletions(-) rename calculate/{variables/old_vars/os => commands}/__init__.py (100%) create mode 100644 calculate/commands/commands.py rename {tests/variables/old_vars/variables => calculate/scripts}/__init__.py (100%) create mode 100644 calculate/scripts/scripts.py delete mode 100644 calculate/variables/old_vars/datavars.py delete mode 100644 calculate/variables/old_vars/main/__init__.py delete mode 100644 calculate/variables/old_vars/os/gentoo/__init__.py delete mode 100644 calculate/variables/old_vars/vars_loader.py create mode 100644 calculate/variables/parameters.py create mode 100755 run_templates.py create mode 100644 tests/commands/__init__.py create mode 100644 tests/commands/test_commands.py create mode 100644 tests/commands/testfiles/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/ini_vars_0.backup create mode 100644 tests/commands/testfiles/gentoo.backup/portage/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/portage/profiles/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/portage/profiles/main/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/20/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/desktop/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/calculate.ini create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/parent create mode 100644 tests/commands/testfiles/gentoo.backup/repos/distros/profiles/calculate.ini create mode 100644 tests/commands/testfiles/variables/main/__init__.py create mode 100644 tests/commands/testfiles/variables/os/__init__.py create mode 100644 tests/commands/testfiles/variables/os/gentoo/__init__.py create mode 100644 tests/commands/testfiles/variables/system/__init__.py create mode 100644 tests/scripts/__init__.py create mode 100644 tests/scripts/test_scripts.py create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_32/install/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/file_0 create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_32/update/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/file_0 create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_33/install/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/file_0 create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_33/update/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/file_0 create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_34/install/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/.calculate_directory create mode 100644 tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/file_0 create mode 100644 tests/templates/testfiles/test_runner/install/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/install/dir_2/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/install/dir_2/file_0 create mode 100644 tests/templates/testfiles/test_runner/install/dir_3/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/install/dir_3/file_0 create mode 100644 tests/templates/testfiles/test_runner/remove/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/remove/test_dir/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/update/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/update/dir_1/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/update/dir_1/file_0 create mode 100644 tests/templates/testfiles/test_runner/update/dir_2/.calculate_directory create mode 100644 tests/templates/testfiles/test_runner/update/dir_2/file_0 delete mode 100644 tests/variables/old_vars/old_vars.py delete mode 100644 tests/variables/old_vars/variables/level/__init__.py delete mode 100644 tests/variables/old_vars/variables/level/level2/__init__.py delete mode 100644 tests/variables/old_vars/variables/main/__init__.py delete mode 100644 tests/variables/old_vars/variables/os/__init__.py rename tests/variables/{test_variables.py => test_datavars.py} (84%) create mode 100644 tests/variables/test_parameters.py create mode 100644 tests/variables/testfiles/parameters_ini/calculate.ini create mode 100644 tests/variables/testfiles/variables_13/main/__init__.py create mode 100644 tests/variables/testfiles/variables_13/os/__init__.py create mode 100644 tests/variables/testfiles/variables_13/os/gentoo/__init__.py create mode 100644 tests/variables/testfiles/variables_13/system/__init__.py create mode 100644 tests/variables/testfiles/variables_14/level/__init__.py create mode 100644 tests/variables/testfiles/variables_14/level/level_2/__init__.py create mode 100644 tests/variables/testfiles/variables_14/main/__init__.py create mode 100644 tests/variables/testfiles/variables_14/os/__init__.py create mode 100644 tests/variables/testfiles/variables_15/main/__init__.py create mode 100644 tests/variables/testfiles/variables_15/os/__init__.py create mode 100644 tests/variables/testfiles/variables_15/os/gentoo/__init__.py create mode 100644 tests/variables/testfiles/variables_15/system/__init__.py create mode 100644 tests/variables/testfiles/variables_16/main/__init__.py create mode 100644 tests/variables/testfiles/variables_16/os/__init__.py create mode 100644 tests/variables/testfiles/variables_16/os/gentoo/__init__.py create mode 100644 tests/variables/testfiles/variables_16/system/__init__.py create mode 100644 tests/variables/testfiles/variables_17/main/__init__.py create mode 100644 tests/variables/testfiles/variables_17/os/__init__.py create mode 100644 tests/variables/testfiles/variables_17/os/gentoo/__init__.py create mode 100644 tests/variables/testfiles/variables_17/system/__init__.py diff --git a/calculate/variables/old_vars/os/__init__.py b/calculate/commands/__init__.py similarity index 100% rename from calculate/variables/old_vars/os/__init__.py rename to calculate/commands/__init__.py diff --git a/calculate/commands/commands.py b/calculate/commands/commands.py new file mode 100644 index 0000000..d14ac5a --- /dev/null +++ b/calculate/commands/commands.py @@ -0,0 +1,175 @@ +from types import FunctionType +from typing import Union, Callable, List +from calculate.scripts.scripts import Script +from calculate.variables.parameters import Parameters +from calculate.variables.datavars import NamespaceNode, DependenceAPI,\ + DependenceError, VariableNotFoundError +from calculate.variables.loader import Datavars +from calculate.utils.io_module import IOModule + + +class CommandCreationError(Exception): + '''Исключение кидаемое при наличии ошибок во время создания объекта + команды.''' + def __init__(self, message: str, command_id: str = '', title: str = ''): + self.message: str = message + self.command_id: str = command_id + self.command_title: str = title + + def __str__(self) -> str: + if self.command_title: + return (f'can not create command {self.command_title}:' + f' {self.message}') + if self.command_id: + return f'can not create command {self.command_id}: {self.message}' + return f'can not create command: {self.message}' + + +class CommandInitializationError(Exception): + '''Исключение кидаемое при наличии ошибок во время инициализации.''' + def __init__(self, message: str, title: str): + self.message: str = message + self.command_title: str = title + + def __str__(self) -> str: + if self.command_title: + return (f'can not initialize command {self.command_title}:' + f' {self.message}') + + +class Command: + '''Класс команды, предназначен для хранения информации о команде и привязки ее к модулю ввода/вывода''' + def __init__(self, command_id: str = '', category: str = '', + title: str = '', + script: Union[Callable, Script, None] = None, + parameters: Union[Parameters, None] = None, + namespace: Union[str, NamespaceNode, None] = None, + command: str = '', gui: bool = False, + icon: Union[str, List[str]] = '', + setvars: dict = {}, rights: List[str] = []): + self._datavars: Union[Datavars, NamespaceNode, None] = None + + if not command_id: + raise CommandCreationError('command ID is not set') + self._id: str = command_id + + # Если собственно команда не была указана, получаем ее из ID. + if command: + self._command: str = command + else: + self._command: str = f'cl_{self._id}' + + if not title: + raise CommandCreationError("title is not set", + command_id=command_id) + self._title: str = title + + if not category: + raise CommandCreationError("category is not set", + command_title=title) + self._category: str = category + + # Пространство имен относительно которого, будет выполняться скрипт. + self._namespace: Union[str, NamespaceNode, None] = namespace + + # Параметры, указываемые при вызове этой команды. + if parameters is None: + raise CommandCreationError("parameters is not set", + command_title=title) + self._parameters: Parameters = parameters + + # Скрипт выполняемый при вызове этой команды. + if not script: + raise CommandCreationError("script is not set", + command_title=title) + elif isinstance(script, FunctionType): + # Поддержка способа описания скрипта в функции. + self._script: Script = script() + else: + self._script: Script = script + + # Параметр указывающий, должена ли данная команда отображаться в + # графическом интерфейсе. + self._gui: bool = gui + + # Устанавливаем название иконки. + if not icon and self._gui: + raise CommandCreationError("icon is not set", + command_title=title) + self._icon: Union[str, List[str]] = icon + + # Словарь с переменными и значениями, которые нужно им присвоить. + self._setvars = setvars + + # Права, необходимые данной команде. + if not rights: + raise CommandCreationError("rights is not set", + title=title) + self._rights = rights + + @property + def script(self) -> Script: + return self._script + + def initialize(self, datavars: Union[Datavars, NamespaceNode], + output: IOModule) -> 'Command': + '''Метод для инициализации всего, что нужно инициализировать в команде. + ''' + self._datavars = datavars + self._output = output + + # Сначала найдем пространство имен. + if self._namespace is not None: + if isinstance(self._namespace, str): + self._namespace = self._find_namespace(self._namespace, + datavars) + + # Инициализируем параметры. + self._parameters.initialize(datavars) + + # Инициализируем скрипт. + self._script.initialize(output, datavars, self._namespace) + + # Устанавливаем переменные, если нужно. + if self._setvars: + self._set_variables(self._setvars, datavars, self._namespace) + + return self + + def _find_namespace(self, namespace_path: str, + datavars: Union[Datavars, NamespaceNode] + ) -> NamespaceNode: + '''Метод для поиска пространств имен.''' + # TODO сделать где-нибудь единственный статический вариант. + parts = namespace_path.split('.') + namespace = datavars + for part in parts: + namespace = namespace[part] + return namespace + + def _set_variables(self, setvars: dict, + datavars: Union[Datavars, NamespaceNode], + namespace: Union[NamespaceNode, None]) -> None: + '''Метод для установки указанных значений, указанным переменным.''' + force = False + + for varname, value in setvars.items(): + if varname.endswith('!'): + varname = varname[:-1] + force = True + else: + force = False + + try: + variable = DependenceAPI.find_variable( + varname, datavars, + current_namespace=namespace) + except VariableNotFoundError: + self._output.set_error(f"Can not set variable '{varname}':" + " variable does not exist.") + + if not variable.readonly or force: + variable.set(value) + else: + self._output.set_error("Can not set readonly variable " + f"'{variable.get_fullname}'.") diff --git a/tests/variables/old_vars/variables/__init__.py b/calculate/scripts/__init__.py similarity index 100% rename from tests/variables/old_vars/variables/__init__.py rename to calculate/scripts/__init__.py diff --git a/calculate/scripts/scripts.py b/calculate/scripts/scripts.py new file mode 100644 index 0000000..d778929 --- /dev/null +++ b/calculate/scripts/scripts.py @@ -0,0 +1,986 @@ +# vim: fileencoding=utf-8 +# +import re +import inspect +from typing import Callable, Any, Union, List, Generator +from calculate.variables.datavars import DependenceAPI, DependenceError,\ + VariableNode, NamespaceNode,\ + HashType, TableType +from calculate.variables.loader import Datavars +from calculate.utils.io_module import IOModule +from collections.abc import Iterable, Mapping + + +class TaskInitializationError(Exception): + pass + + +class ScriptError(Exception): + pass + + +class TaskError(Exception): + def __init__(self, message, handlers=set()): + super().__init__(message) + self.handlers = handlers + + +class ActionError(Exception): + '''Класс исключения для создания штатных исключений в функциях действий.''' + pass + + +class ConditionError(Exception): + pass + + +class Script: + '''Класс скрипта, собирает задачи, обработчики, блоки и прочее. Принимает + аргументы.''' + def __init__(self, script_id: str, args: List[Any] = [], + success_message: str = None, + failed_message: str = None, + interrupt_message: str = None): + self._id: str = script_id + self._items: List[Union['Task', 'Block', 'Handler', 'Run']] = None + + self._args_names: list = args + self._args_vars: list = [] + + self._success_message: str = success_message + self._failed_message: str = failed_message + self._interrupt_message: str = interrupt_message + + self._output: IOModule = None + self._datavars: Union[Datavars, NamespaceNode] = None + self._namespace: Union[NamespaceNode, None] = None + + self._handlers: set = set() + self._initialized: bool = False + + def tasks(self, *items: List[Union['Task', 'Block', 'Handler', 'Run']] + ) -> 'Script': + '''Метод для указания задач и контейнеров, из которых состоит скрипт. + ''' + self._items = items + return self + + def initialize(self, output: IOModule, + datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode = None) -> 'Script': + '''Метод для инициализации, состоящей в установке модулей вывода, + модуля переменных, текущего пространства имен, а также создании + переменных скрипта.''' + self._output = output + self._datavars = datavars + self._namespace = namespace + + if not self._initialized: + self._script_namespace, self._args_vars =\ + self.make_script_variables(self._id, + self._args_names, + self._datavars) + self._initialized = True + return self + + def run(self) -> None: + '''Метод для запуска задач, содержащихся в данном скрипте.''' + essential_error = None + handlers_to_run = [] + + for task_item in self._items: + if isinstance(task_item, Handler): + if task_item.id in self._handlers: + handlers_to_run.append(task_item) + elif essential_error is None: + try: + output = task_item.run(self._output, + self._datavars, + self._script_namespace, + self._namespace) + self._handlers.update(output) + except Exception as error: + if isinstance(error, TaskError): + self._handlers.update(error.handlers) + essential_error = error + + for handler in handlers_to_run: + try: + task_item.run(self._output, self._datavars, + self._script_namespace, self._namespace) + except Exception as error: + # TODO Разобраться что делать с ошибками в обработчике. + self._output.set_error(f"error in handler '{task_item.id}':" + f" {str(error)}") + if essential_error is not None: + raise essential_error + + @staticmethod + def make_script_variables(script_id, + args: List[Any], + datavars: Union[Datavars, NamespaceNode] + ) -> NamespaceNode: + '''Метод для создания переменных скрипта. Создает пространство имен + скрипта и переменных аргументов.''' + if 'scripts' not in datavars: + scripts = Script.make_scripts_namespace(datavars) + else: + scripts = datavars.scripts + + if script_id not in scripts: + current_script = NamespaceNode(script_id) + scripts.add_namespace(current_script) + else: + current_script = scripts[script_id] + current_script.clear() + + args_vars = [] + for arg in args: + args_vars.append(VariableNode(arg, current_script)) + return current_script, args_vars + + @staticmethod + def make_scripts_namespace(datavars: Union[Datavars, NamespaceNode] + ) -> NamespaceNode: + '''Метод создающий пространства имен, необходимые для работы задач.''' + # Для тестирования скорее. + scripts = NamespaceNode('scripts') + datavars.add_namespace(scripts) + + env = NamespaceNode('env') + scripts.add_namespace(env) + + loop = NamespaceNode('loop') + env.add_namespace(loop) + + return scripts + + def __call__(self, *args): + '''Метод для запуска скрипта с аргументами.''' + if len(args) < len(self._args_names): + raise ScriptError((f"script '{self._id}' missing" + f" {len(self._args_names) - len(args)} required" + " arguments: '") + + "', '".join(self._args_names[len(args):]) + "'") + elif len(args) > len(self._args_names): + raise ScriptError(f"script '{self._id}' takes" + f" {len(self._args_names)} arguments, but" + f" {len(args)} were given") + + args_values = self._get_args_values(args, self._datavars, + self._namespace) + for arg_variable, arg_value in zip(self._args_vars, args_values): + arg_variable.source = arg_value + self.run() + + def _get_args_values(self, args: List[Any], + datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode): + '''Метод для получения значений аргументов.''' + args_values = [] + for argument in args: + if isinstance(argument, str): + variable = DependenceAPI.find_variable( + argument, datavars, + current_namespace=namespace) + args_values.append(variable.get_value()) + elif isinstance(argument, Static): + args_values.append(argument.value) + else: + args_values.append(argument) + return args_values + + +class Run: + '''Класс запускателя скриптов.''' + def __init__(self, script: Script, namespace: str = None, + args: List[Any] = [], when: 'Var' = None, + essential: bool = True): + self._script: Script = script + self._namespace: str = namespace + self._args: List[Any] = args + + self._condition: Var = when + + self._essential = essential + + def run(self, output: IOModule, datavars: Union[Datavars, NamespaceNode], + parent_namespace: NamespaceNode, namespace: NamespaceNode) -> set: + '''Метод для запуска скрипта, указанного имеющегося в запускателе.''' + if self._condition is None or self._condition(datavars, + namespace, + parent_namespace): + # пространство имен или наследуем, либо берем по указанному пути. + if not self._namespace: + self._namespace = namespace + elif isinstance(self._namespace, str): + parts = self._namespace.split('.') + parts.reverse() + self._namespace = datavars[parts.pop()] + while parts: + self._namespace = self._namespace[parts.pop()] + + try: + # if not self._script._initialized: + self._script.initialize(output, datavars, self._namespace) + self._script(*self._args) + except (ScriptError, TaskError) as error: + if self._essential: + raise ScriptError(f"essential script '{self._script._id}'" + f" is failed. Reason: {error}") + output.set_error(f"essential script '{self._script._id}'" + f" is failed. Reason: {error}") + return set() + + +class Static: + '''Класс, оборачивающий значение, которое не должно восприниматься как + переменная.''' + def __init__(self, value: Any): + self._value: Any = value + + @property + def value(self) -> Any: + return self._value + + +class Task: + '''Класс реализующий задачи -- объекты, исполняющие указанные функции + действия action, и содержащий всю необходимую информацию для их работы.''' + def __init__(self, **kwargs): + if 'id' in kwargs: + self._id: str = kwargs['id'] + else: + raise TaskInitializationError("id is not set for task") + + # Имя не обязательно. + self._name: str = kwargs.get('name', None) + + if 'action' in kwargs: + self._action = kwargs['action'] + else: + raise TaskInitializationError("action is not set for task" + f" '{self._name or self._id}'") + + # Список аргументов и их типы + self._args_sources: Union[tuple, list] = kwargs.get('args', []) + self._args_types: List[type] = [] + self._arg_names: List[str] = [] + self._args: Union[tuple, list] = [] + + # Переменные, которым через set будет присвоен результат работы action. + self._set: Union[list, tuple] = kwargs.get('set', None) + self._return_type: type = None + + # Условие выполнения задачи. + self._condition: Var = kwargs.get('when', None) + # Существенность задачи. Если задача существенная, ошибка в ней + # приведет к остановке скрипта, если несущественная -- ошибка будет + # записана в переменную этой задачи, а скрипт будет выполняться дальше. + self._essential: bool = kwargs.get('essential', True) + + # Параметр для указания обработчиков, которые нужно запустить в конце + # выполнения скрипта. + self._handlers = set() + handlers: set = kwargs.get('notify', set()) + if handlers: + if isinstance(handlers, (tuple, list)): + self._handlers.update(handlers) + else: + self._handlers.add(handlers) + + self._initialized: bool = False + + self._namespace: NamespaceNode = None + self._script_namespace: NamespaceNode = None + self._datavars: Union[Datavars, NamespaceNode] = None + self._output: IOModule = None + + @property + def essential(self) -> bool: + return self._essential + + @property + def initialized(self) -> bool: + return self._initialized + + def _initialize(self, output: IOModule, + datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> 'Task': + '''Метод для инициализации задач, привязывает задачу к указанному + модулю переменных, модулю вывода, формирует список аргументов, + определяет их типы и находит переменные, к которым в последствии + нужно будет присвоить значение, возвращенное action.''' + self._datavars = datavars + self._output = output + self._namespace = namespace + self._script_namespace = script_namespace + + if not self._initialized: + self._arg_names,\ + self._args_types,\ + self._return_type = self._get_action_info(self._action) + self._args = self._update_arguments(self._args_sources) + + if self._set is not None: + self._set = self._get_set_variables(self._set, datavars, + namespace) + self._initialized = True + else: + self._args = self._update_arguments(self._args_sources) + + return self + + def run(self, output: IOModule, + datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> set: + '''Метод для запуска данной задачи.''' + self._initialize(output, datavars, script_namespace, namespace) + + if (self._condition is None or self._condition( + self._datavars, + self._namespace, + self._script_namespace)): + arguments_values = self._get_args_values(self._args, + self._args_types) + try: + result = self._action(*arguments_values) + self._create_task_var(result=result, + success=True) + + if self._return_type: + self._check_return_type(result) + + if self._set: + self._set_variables(result) + + return self._handlers + except Exception as error: + if self._essential and not isinstance(error, ActionError): + raise TaskError( + f"essential task '{self._name or self._id}' is" + f" failed. Reason: {error}") + self._create_task_var(success=False, + error_message=str(error)) + return set() + + def _get_action_info(self, action: Callable): + '''Метод для получения информации о функции действия.''' + arguments = [arg.name for arg in inspect.signature( + action).parameters.values()] + annotation = action.__annotations__ + action_types = [] + for argument in arguments: + action_types.append(annotation.get(argument, None)) + + return arguments, action_types, annotation.get('return', None) + + def _update_arguments(self, args_sources: List[Any]) -> tuple: + '''Метод для обновления и установки аргументов функции действия.''' + arguments = [] + # Проверяем количество аргументов. + args_length = len(self._arg_names) + if 'output' in self._arg_names: + if (args_length - 1) != len(self._args_sources): + raise TaskInitializationError(f"action function takes" + f" {args_length - 1} arguments" + f" but {len(args_sources)}" + " is given") + elif args_length != len(self._args_sources): + raise TaskInitializationError(f"action function takes" + f" {args_length} arguments but" + f" {len(args_sources)} is given") + + # Формируем список значений аргументов. + args_iterator = iter(args_sources) + + for arg_name in self._arg_names: + if arg_name == 'output': + arguments.append(self._output) + else: + argument = next(args_iterator) + if isinstance(argument, str): + try: + variable = DependenceAPI.find_variable( + argument, + self._datavars, + current_namespace=self._namespace) + arguments.append(variable) + except DependenceError as error: + raise TaskInitializationError(str(error)) + elif isinstance(argument, Static): + arguments.append(argument.value) + else: + arguments.append(argument) + return tuple(arguments) + + def _get_args_values(self, args: List[Any], + args_types: List[type]) -> List[Any]: + '''Метод для получения значений из списка аргументов функции action.''' + arguments_values = [] + for arg, arg_type in zip(args, args_types): + if isinstance(arg, VariableNode): + value = arg.get_value() + if arg.variable_type is HashType: + value = value.get_hash() + elif arg.variable_type is TableType: + value = value.get_table() + + arguments_values.append(self._check_type(value, arg_type)) + else: + arguments_values.append(self._check_type(arg, arg_type)) + return arguments_values + + def _check_type(self, value: Any, arg_type: type) -> Any: + '''Метод для проверки типа аргумента.''' + if arg_type is None or isinstance(value, arg_type): + return value + else: + raise TaskError(f"'{arg_type}' is expected, not {type(value)}") + + def _create_task_var(self, + result: Any = None, + success: bool = True, + error_message: str = None) -> None: + '''Метод для создания переменной задачи, в которую отправляются + результаты вычислений, имеющихся в функции action.''' + VariableNode(self._id, self._script_namespace, variable_type=HashType, + source={'result': result, + 'success': success, + 'error_message': error_message}) + + def _get_set_variables(self, to_set: Union[str, list, tuple, dict], + datavars: Union[NamespaceNode, Datavars], + namespace: NamespaceNode) -> Union[str, list, dict]: + '''Метод для поиска переменных, в которые нужно положить значения, + полученные в результате вычислений в action.''' + if isinstance(to_set, str): + return DependenceAPI.find_variable(to_set, datavars, + current_namespace=namespace) + elif isinstance(to_set, (list, tuple)): + vars_to_set = [] + for var in to_set: + vars_to_set.append( + DependenceAPI.find_variable( + var, datavars, + current_namespace=namespace)) + return vars_to_set + elif isinstance(to_set, dict): + for key in to_set: + variable = DependenceAPI.find_variable( + to_set[key], datavars, + current_namespace=namespace) + to_set[key] = variable + return to_set + else: + raise TaskError(f"set value must be {dict}, {list}, {tuple} or " + f"{str}, not {type(to_set)}") + + def _set_variables(self, result: Any) -> None: + '''Метод для присвоения переменным результатов работы функции action. + ''' + self._check_set_vars_types(result) + + if isinstance(self._set, dict): + if not isinstance(result, dict): + raise TaskError(f"set parameter value is '{dict}' but action" + f" result is '{type(result)}'") + for key in self._set: + if key in result: + self._set[key].set(result[key]) + elif isinstance(self._set, (list, tuple)): + for var in self._set: + var.set(result) + else: + self._set.set(result) + + def _check_set_vars_types(self, result: Any) -> None: + '''Метод для проверки соответствия типов значений, полученных из + action, и переменных, в которые нужно положить эти значения.''' + # Теперь проверяем соответствие типа переменных из set и типа значений. + if isinstance(self._set, dict): + for key, variable in self._set.items(): + if (key in result and not + isinstance(result[key], + variable.variable_type.python_type)): + raise TaskError("can not set value of" + f" '{type(result[key])}' type to the" + " variable of" + f" '{variable.variable_type.name}'" + " type") + elif isinstance(self._set, (list, tuple)): + for variable in self._set: + if not isinstance(result, variable.variable_type.python_type): + raise TaskError(f"can not set value of '{type(result)}'" + " type to the variable of" + f" '{variable.variable_type.name}' type") + else: + if not isinstance(result, self._set.variable_type.python_type): + raise TaskError(f"can not set value of '{type(result)}'" + " type to the variable of" + f" '{self._set.variable_type.name}' type") + + def _check_return_type(self, result: Any) -> None: + '''Метод для проверки типа значения, возвращенного функцией action.''' + if not isinstance(result, self._return_type): + raise TaskError(f"action returned value of '{type(result)}' " + f"type, but expected is '{self._return_type}'") + + +class Loop: + '''Базовый класс всех объектов цикла для блоков.''' + def initialize_loop(self, datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для инициализации объекта цикла.''' + pass + + def get_looper(self) -> Generator[bool, bool, None]: + '''Генератор, предназначенный для управления циклом при запуске блока + задач.''' + yield True + yield False + + +class For(Loop): + '''Класс цикла блока, использующего указанное итерируемое значение.''' + def __init__(self, value: str, iter_arg: Union[str, Iterable]): + self._iter_arg = iter_arg + self._item_name = value + + self._iterable_value = None + self._item_variable = None + self._initialized = False + + def initialize_loop(self, datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для инициализации объекта цикла.''' + self._iterable_value = self._get_iterable_value(self._iter_arg, + datavars, + namespace) + self._item_variable = self._get_item_variable(self._item_name, + script_namespace) + self._initialized = True + + def get_looper(self) -> Generator[bool, bool, None]: + '''Генератор, предназначенный для управления циклом при запуске блока + задач.''' + if not self._initialized: + yield False + else: + for item in self._iterable_value: + self._item_variable.source = item + yield True + yield False + + def _get_iterable_value(self, iter_argument: Union[str, Iterable], + datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode) -> Iterable: + '''Метод для получения и проверки значения, которое будем далее + итерировать.''' + value = self._iter_arg + + if isinstance(self._iter_arg, str): + variable = DependenceAPI.find_variable(self._iter_arg, + datavars, + current_namespace=namespace) + value = variable.get_value() + if variable.variable_type is HashType: + value = value.get_hash() + elif variable.variable_type is TableType: + value = value.table_hash() + + if not isinstance(value, Iterable): + raise TaskError("For() argument '{value}' is not iterable.") + if isinstance(value, Mapping): + value = value.items() + return value + + def _get_item_variable(self, item_name: str, + script_namespace: NamespaceNode) -> VariableNode: + '''Метод для получения или создания переменной, в которой будет + храниться текущее значение, взятое из итерируемого значения.''' + if item_name in script_namespace: + item_variable = script_namespace[item_name] + else: + item_variable = VariableNode(item_name, script_namespace) + return item_variable + + +class While(Loop): + '''Класс цикла блока, аналогичного while.''' + def __init__(self, condition: 'Var'): + self._condition: Var = condition + + self._datavars: Union[Datavars, NamespaceNode] = None + self._namespace: NamespaceNode = None + + def initialize_loop(self, datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для инициализации объекта цикла.''' + self._datavars: Union[Datavars, NamespaceNode] = datavars + self._namespace: Union[NamespaceNode, None] = namespace + self._script_namespace: NamespaceNode = script_namespace + + def get_looper(self) -> Generator[bool, bool, None]: + '''Генератор, предназначенный для управления циклом при запуске блока + задач.''' + while True: + condition_result = self._condition(self._datavars, + self._namespace, + self._script_namespace) + yield condition_result + + +class Until(Loop): + '''Класс цикла блока, аналогичного конструкции do ... while.''' + def __init__(self, condition: 'Var'): + self._condition: Var = condition + + self._datavars: Union[Datavars, NamespaceNode] = None + self._namespace: Datavars = None + + def initialize_loop(self, datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для инициализации объекта цикла.''' + self._datavars: Union[Datavars, NamespaceNode] = datavars + self._namespace: Union[NamespaceNode, None] = namespace + self._script_namespace: NamespaceNode = script_namespace + + def get_looper(self) -> Generator[bool, bool, None]: + '''Генератор, предназначенный для управления циклом при запуске блока + задач.''' + yield True + while True: + yield self._condition(self._datavars, + self._namespace, + self._script_namespace) + + +class Block: + '''Класс блока задач.''' + def __init__(self, *tasks: List[Task], when: Union['Var', None] = None, + loop: Loop = Loop()): + self._tasks: List[Task] = tasks + self._rescue_tasks: List[Task] = None + + self._condition: 'Var' = when + + if not isinstance(loop, Loop): + raise TaskError('loop block parameter must be Loop type') + self._loop: Loop = loop + + def run(self, output: IOModule, + datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для запуска выполнения задач, содержащихся в блоке.''' + handlers = set() + + if (self._condition is None or self._condition(datavars, namespace, + script_namespace)): + self._loop.initialize_loop(datavars, script_namespace, namespace) + looper = self._loop.get_looper() + while next(looper): + for task in self._tasks: + try: + output = task.run(output, datavars, script_namespace, + namespace) + handlers.update(output) + except Exception as error: + if self._rescue_tasks is not None: + self._run_rescue(output, datavars, + script_namespace, namespace) + if isinstance(error, TaskError): + error.handlers.update(handlers) + raise error + return handlers + + def _run_rescue(self, output: IOModule, + datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для запуска задач, указанных в rescue. Эти задачи выполняются, + если возникает ошибка при выполнении важной задачи, содержащейся в + блоке.''' + for task in self._rescue_tasks: + try: + task.run(output, datavars, script_namespace, namespace) + except Exception: + # TODO разобраться с тем, что делать если ошибка закралась в + # задачи из rescue. + pass + + def rescue(self, *tasks: List[Task]) -> 'Block': + '''Метод для задания задач, выполняемых, если некоторая важная задача, + содержащаяся в блоке выполняется с ошибкой.''' + self._rescue_tasks = tasks + return self + + +class Handler: + '''Класс обработчика -- объекта скрипта, имеющего идентификатор, + запускаемого в конце выполнения скрипта в том случае, если его + идентификатор присутствовал в параметре notify хотя бы у одной успешно + выполненной задачи.''' + def __init__(self, handler_id, *tasks: List[Task]): + self._id: str = handler_id + self._tasks: List[Task] = tasks + + @property + def id(self) -> str: + return self._id + + def run(self, output: IOModule, datavars: Union[Datavars, NamespaceNode], + script_namespace: NamespaceNode, + namespace: NamespaceNode) -> None: + '''Метод для запуска выполнения задач, содержащихся в обработчике.''' + handlers = set() + for task in self._tasks: + try: + handlers.update(task.run(output, datavars, script_namespace, + namespace)) + except Exception as error: + output.set_error(f"error during execution handle '{self._id}'" + f" in task '{task._name or task._id}':" + f" {str(error)}") + return handlers + + +class ConditionValue: + '''Базовый класс для всех объектов, предназначенных для создания условий. + ''' + def __init__(self, value: Any): + self.value: Any = value + + def get(self, datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode, + script_namespace: NamespaceNode) -> Any: + return self.value + + +class Var(ConditionValue): + '''Класс для создания условий выполнения задач.''' + def __init__(self, *variable: List[VariableNode], + value: Any = None, other: Any = None, + tasks: Union[List[str], None] = None, + function: Union[Callable, None] = None): + if variable: + self.variable: Union[VariableNode, str, None] = variable[0] + else: + self.variable = None + + self.value: Any = value + self.other: Any = other + self.function: Callable = function + + self.tasks: List[str] = tasks + + def get(self, datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode, + script_namespace: NamespaceNode) -> Any: + '''Метод для расчета итогового значения ноды условия выполнения задачи. + ''' + if self.function is not None: + if self.tasks: + return self.function(self.tasks, script_namespace) + elif self.other is not None: + return self.function(self.get_value(datavars, namespace, + script_namespace), + self.get_other(datavars, namespace, + script_namespace)) + else: + return self.function(self.get_value(datavars, namespace, + script_namespace)) + return self.get_value(datavars, namespace, script_namespace) + + def get_value(self, datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode, + script_namespace: NamespaceNode) -> Any: + '''Метод для получения собственного значения ноды.''' + if self.value is not None: + return self.value.get(datavars, namespace, script_namespace) + elif self.variable is not None: + if isinstance(self.variable, str): + self.variable = DependenceAPI.find_variable( + self.variable, datavars, + current_namespace=namespace) + return self.variable.get_value() + else: + raise ConditionError('Can not get value for condition') + + def get_other(self, datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode, + script_namespace: NamespaceNode) -> Any: + '''Метод для получения значения ноды, необходимой для выполнения + бинарной операции.''' + if isinstance(self.other, ConditionValue): + return self.other.get(datavars, namespace, script_namespace) + else: + return self.other + + def __call__(self, datavars: Union[Datavars, NamespaceNode], + namespace: NamespaceNode, + script_namespace: NamespaceNode) -> Any: + return self.get(datavars, namespace, script_namespace) + + def __eq__(self, other: Any) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x == y) + + def __ne__(self, other: Any) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x != y) + + def __lt__(self, other: Any) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x < y) + + def __gt__(self, other: Any) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x > y) + + def __le__(self, other: Any) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x <= y) + + def __ge__(self, other: Any) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x >= y) + + def __and__(self, other: bool) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x and y) + + def __or__(self, other: bool) -> 'Var': + return Var(value=self, other=other, function=lambda x, y: x or y) + + def __invert__(self) -> 'Var': + return Var(value=self, function=lambda x: not x) + + def __lshift__(self, other: Any) -> 'Var': + '''Метод для переопределения операции <<, которая теперь играет роль + in.''' + return Var(value=self, other=other, function=lambda x, y: y in x) + + def has(self, other: Any) -> 'Var': + '''Метод аналогичный операции in.''' + return Var(value=self, other=other, function=lambda x, y: y in x) + + def match(self, pattern: str) -> 'Var': + '''Метод для проверки с помощью регулярки, если в строке нечто, + соответствующее паттерну.''' + return Var(value=self, other=pattern, + function=lambda x, y: bool(re.search(y, x))) + + def regex(self, pattern: str, repl: str) -> 'Var': + '''Метод для реализации sub''' + return Var(value=self, other=(pattern, repl), + function=lambda x, y: re.sub(*y, x)) + + def replace(self, original: str, repl: str) -> 'Var': + '''Метод для реализации replace.''' + return Var(value=self, other=(original, repl), + function=lambda x, y: x.replace(*y)) + + def __repr__(self) -> 'Var': + return ("") + + +def Done(*tasks: List[str]) -> Var: + '''Функция создающая объект Var, получающий информацию о том, все ли + указанные методы выполнены.''' + return Var(tasks=tasks, + function=_is_done) + + +def DoneAny(*tasks: List[str]) -> Var: + '''Функция создающая объект Var, получающий информацию о том, есть ли + среди указанных методов те, которые выполнены.''' + return Var(tasks=tasks, + function=_is_done_any) + + +def Success(*tasks: List[str]) -> Var: + '''Функция создающая объект Var, получающий информацию о том, все ли + указанные методы выполнены и притом выполнены успешно.''' + return Var(tasks=tasks, + function=_is_success) + + +def SuccessAny(*tasks: List[str]) -> Var: + '''Функция создающая объект Var, получающий информацию о том, есть ли + среди указанных методов те, которые выполнены успешно.''' + return Var(tasks=tasks, + function=_is_success_any) + + +def Failed(*tasks: List[str]) -> Var: + '''Функция создающая объект Var, получающий информацию о том, все ли + указанные методы не выполнены или выполнены с ошибкой.''' + return Var(tasks=tasks, + function=_is_failed) + + +def FailedAny(*tasks: List[str]) -> Var: + '''Функция создающая объект Var, получающий информацию о том, есть ли + среди указанных методов те, которые не выполнены или выполнены с ошибкой. + ''' + return Var(tasks=tasks, + function=_is_failed_any) + + +def _is_done(tasks, script_namespace: NamespaceNode) -> bool: + '''Функция проверяющая по содержимому пространства имен tasks все ли методы + из указанных выполнены.''' + for task in tasks: + if task not in script_namespace: + return False + return True + + +def _is_done_any(tasks, script_namespace: NamespaceNode) -> bool: + '''Функция проверяющая по содержимому пространства имен tasks есть ли среди + указанных методов те, которые выполнены.''' + for task in tasks: + if task in script_namespace: + return True + return False + + +def _is_success(tasks, script_namespace: NamespaceNode) -> bool: + '''Функция проверяющая по содержимому пространства имен tasks все ли методы + из указанных выполнены успешно.''' + for task in tasks: + if (task not in script_namespace or + not script_namespace[task].get_value().get_hash()['success']): + return False + return True + + +def _is_success_any(tasks, script_namespace: NamespaceNode) -> bool: + '''Функция проверяющая по содержимому пространства имен tasks есть ли среди + указанных методов те, которые выполнены успешно.''' + for task in tasks: + if (task in script_namespace and + script_namespace[task].get_value().get_hash()['success']): + return True + return False + + +def _is_failed(tasks, script_namespace: NamespaceNode) -> bool: + '''Функция проверяющая по содержимому пространства имен tasks все ли методы + из указанных не выполнены или выполнены с ошибкой.''' + for task in tasks: + if (task in script_namespace and + script_namespace[task].get_value().get_hash()['success']): + return False + return True + + +def _is_failed_any(tasks, script_namespace: NamespaceNode) -> bool: + '''Функция проверяющая по содержимому пространства имен tasks есть ли среди + указанных методов те, которые не выполнены или выполнены с ошибкой.''' + for task in tasks: + if (task not in script_namespace or + not script_namespace[task].get_value().get_hash()['success']): + return True + return False diff --git a/calculate/templates/template_engine.py b/calculate/templates/template_engine.py index c7be078..3532120 100644 --- a/calculate/templates/template_engine.py +++ b/calculate/templates/template_engine.py @@ -244,15 +244,24 @@ class ParametersProcessor: def check_package_parameter(self, parameter_value): try: - atom_object = self.package_atom_parser.parse_package_parameter( + if isinstance(parameter_value, str): + result = self.package_atom_parser.parse_package_parameter( parameter_value) + elif isinstance(parameter_value, list): + result = [] + for atom in parameter_value: + result.append( + self.package_atom_parser.parse_package_parameter( + atom) + ) + except PackageAtomError as error: if error.errno == NOTEXIST: raise ConditionFailed(error.message, self.lineno) else: raise IncorrectParameter(error.message) - return atom_object + return result def check_append_parameter(self, parameter_value): if parameter_value not in self.available_appends: @@ -754,18 +763,18 @@ class CalculateExtension(Extension): '''Класс расширения для jinja2, поддерживающий теги calculate-шаблонов.''' _parameters_set = set() - parameters_processor = None - # Виды операций в теге save. ASSIGN, APPEND, REMOVE = range(3) - def __init__(self, environment, datavars_module=Variables()): + def __init__(self, environment, parameters_processor: ParametersProcessor, + datavars_module=Variables()): super().__init__(environment) self.environment = environment self.environment.globals.update({'pkg': self.pkg}) self._datavars = datavars_module + self.parameters_processor = parameters_processor self.template_type = DIR self.tags = {'calculate', 'save', 'set_var'} @@ -782,6 +791,8 @@ class CalculateExtension(Extension): 'save': self.parse_save} def __call__(self, env): + # Необходимо для обеспечения возможности передать готовый объект + # расширения, а не его класс. return self def parse(self, parser): @@ -1245,9 +1256,6 @@ class TemplateEngine: CalculateExtension._parameters_set =\ ParametersProcessor.available_parameters - CalculateExtension._datavars = datavars_module - - self.available_appends = appends_set ParametersProcessor.available_appends = appends_set @@ -1259,8 +1267,6 @@ class TemplateEngine: chroot_path=chroot_path, datavars_module=datavars_module) - CalculateExtension.parameters_processor = self.parameters_processor - if directory_path is not None: self.environment = Environment( loader=FileSystemLoader(directory_path)) @@ -1269,6 +1275,7 @@ class TemplateEngine: self.calculate_extension = CalculateExtension( self.environment, + self.parameters_processor, datavars_module=datavars_module) self.environment.add_extension(self.calculate_extension) @@ -1313,8 +1320,7 @@ class TemplateEngine: if parameters is not None: self._parameters_object = parameters else: - self._parameters_object = ParametersContainer( - parameters_dictionary={}) + self._parameters_object = ParametersContainer() if self._parameters_object.env: CalculateContext._env_set = self._parameters_object.env.copy() diff --git a/calculate/templates/template_processor.py b/calculate/templates/template_processor.py index e24c6b1..3e313f8 100644 --- a/calculate/templates/template_processor.py +++ b/calculate/templates/template_processor.py @@ -2,13 +2,15 @@ # from pprint import pprint from ..utils.package import PackageAtomParser, Package, PackageNotFound,\ - PackageAtomName, Version + PackageAtomName, Version, NonePackage from ..utils.files import join_paths, write_file, read_file_lines, FilesError,\ check_directory_link, read_link, Process,\ get_target_from_link from .template_engine import TemplateEngine, Variables, ConditionFailed,\ ParametersProcessor, DIR, FILE,\ ParametersContainer +from calculate.variables.datavars import StringType, ListType, NamespaceNode +from calculate.variables.loader import Datavars from .format.base_format import Format from ..utils.io_module import IOModule from collections import OrderedDict, abc @@ -153,7 +155,8 @@ class TemplateWrapper: template_text='', target_package=None, chroot_path='/', - config_archive_path='/var/lib/calculate/config-archive'): + config_archive_path='/var/lib/calculate/config-archive', + dbpkg=True): self.target_path = target_file_path self.template_path = template_path self.chroot_path = chroot_path @@ -188,6 +191,10 @@ class TemplateWrapper: # Пакет, к которому относится файл. self.target_package = target_package + # Флаг, разрешающий работу с CONTENTS. Если False, то выключает + # protected для всех файлов блокирует все операции с CONTENTS и ._cfg. + self.dbpkg = dbpkg + # Флаг, указывающий, что файл является PROTECTED. self.protected = False @@ -349,10 +356,12 @@ class TemplateWrapper: # Если для шаблона и целевого файла никаким образом не удается # определить пакет и есть параметр append -- шаблон пропускаем. if parameter_package is None and file_package is None: - if self.parameters.append: + if self.parameters.append and self.parameters.append != 'skip': raise TemplateCollisionError( "'package' parameter is not defined for" " template with 'append' parameter.") + else: + return elif parameter_package is None: self.target_package_name = file_package @@ -383,16 +392,19 @@ class TemplateWrapper: # Проверим, является ли файл защищенным. # Сначала проверяем по переменной CONFIG_PROTECT. - for protected_path in self._protected_set: - if self.target_path.startswith(protected_path): - self.protected = True - break + if self.dbpkg: + for protected_path in self._protected_set: + if self.target_path.startswith(protected_path): + self.protected = True + break - # Затем по переменной CONFIG_PROTECT_MASK. - for unprotected_path in self._unprotected_set: - if self.target_path.startswith(unprotected_path): - self.protected = False - break + # Затем по переменной CONFIG_PROTECT_MASK. + for unprotected_path in self._unprotected_set: + if self.target_path.startswith(unprotected_path): + self.protected = False + break + else: + self.protected = False # Собираем список имеющихся ._cfg файлов. cfg_pattern = os.path.join(os.path.dirname(self.target_path), @@ -586,7 +598,9 @@ class TemplateExecutor: def __init__(self, datavars_module=Variables(), chroot_path='/', cl_config_archive='/var/lib/calculate/config-archive', cl_config_path='/var/lib/calculate/config', - execute_archive_path='/var/lib/calculate/.execute/'): + execute_archive_path='/var/lib/calculate/.execute/', + dbpkg=True): + # TODO добавить список измененных файлов. self.datavars_module = datavars_module self.chroot_path = chroot_path @@ -598,6 +612,8 @@ class TemplateExecutor: self.execute_archive_path = execute_archive_path self.execute_files = OrderedDict() + self.dbpkg = dbpkg + # Список целевых путей измененных файлов. Нужен для корректиного # формирования calculate-заголовка. self.processed_targets = [] @@ -661,7 +677,8 @@ class TemplateExecutor: template_text=template_text, target_package=target_package, chroot_path=self.chroot_path, - config_archive_path=self.cl_config_archive_path) + config_archive_path=self.cl_config_archive_path, + dbpkg=self.dbpkg) except TemplateTypeConflict as error: raise TemplateExecutorError("type conflict: {}".format(str(error))) @@ -677,7 +694,8 @@ class TemplateExecutor: else: self._remove_file(template_object.target_path) - template_object.remove_from_contents() + if self.dbpkg: + template_object.remove_from_contents() template_object.target_type = None # Если был включен mirror, то после удаления файла завершаем @@ -727,7 +745,8 @@ class TemplateExecutor: if template_object.target_type is None: self._create_directory(template_object) - template_object.add_to_contents() + if self.dbpkg: + template_object.add_to_contents() def _append_remove_directory(self, template_object: TemplateWrapper) -> None: @@ -736,7 +755,8 @@ class TemplateExecutor: if template_object.target_type is not None: self._remove_directory(template_object.target_path) - template_object.remove_from_contents() + if self.dbpkg: + template_object.remove_from_contents() def _append_skip_directory(self, template_object: TemplateWrapper) -> None: @@ -759,7 +779,8 @@ class TemplateExecutor: self._chown_directory(template_object.target_path, template_object.parameters.chown) - template_object.clear_dir_contents() + if self.dbpkg: + template_object.clear_dir_contents() def _append_link_directory(self, template_object: TemplateWrapper) -> None: '''Метод описывающий действия для append = "link", если шаблон -- @@ -776,7 +797,8 @@ class TemplateExecutor: self._chown_directory(template_object.parameters.source, template_object.parameters.chown) - template_object.add_to_contents() + if self.dbpkg: + template_object.add_to_contents() def _append_replace_directory(self, template_object: TemplateWrapper) -> None: @@ -784,10 +806,12 @@ class TemplateExecutor: директория. Очищает директорию или создает, если ее нет.''' if template_object.target_type is None: self._create_directory(template_object) - template_object.add_to_contents() + if self.dbpkg: + template_object.add_to_contents() else: self._clear_directory(template_object.target_path) - template_object.clear_dir_contents() + if self.dbpkg: + template_object.clear_dir_contents() def _append_join_file(self, template_object: TemplateWrapper, join_before=False, replace=False) -> None: @@ -879,21 +903,22 @@ class TemplateExecutor: if chmod: self._chmod_file(save_path, chmod) - # Убираем все ._cfg файлы. - if template_object.cfg_list: - for cfg_file_path in template_object.cfg_list: - self._remove_file(cfg_file_path) + if self.dbpkg: + # Убираем все ._cfg файлы. + if template_object.cfg_list: + for cfg_file_path in template_object.cfg_list: + self._remove_file(cfg_file_path) - # Убираем целевой файл из CL. - self.calculate_config_file.remove_file( + # Убираем целевой файл из CL. + self.calculate_config_file.remove_file( template_object.target_path) - # Обновляем CONTENTS. - if template_object.protected: - if template_object.parameters.unbound: - template_object.remove_from_contents() - else: - template_object.add_to_contents( + # Обновляем CONTENTS. + if template_object.protected: + if template_object.parameters.unbound: + template_object.remove_from_contents() + else: + template_object.add_to_contents( file_md5=output_text_md5) else: changed_files = parsed_template.execute_format( @@ -904,7 +929,8 @@ class TemplateExecutor: # Если исполняемый формат выдал список измененных файлов для # изменения CONTENTS и при этом задан пакет -- обновляем # CONTENTS. - if changed_files and template_object.target_package: + if (self.dbpkg and changed_files and + template_object.target_package): template_object.update_contents_from_list(changed_files) else: if template_object.target_type is not None and not replace: @@ -993,7 +1019,8 @@ class TemplateExecutor: if template_object.target_type is not None: self._remove_file(template_object.target_path) - template_object.remove_from_contents() + if self.dbpkg: + template_object.remove_from_contents() def _append_clear_file(self, template_object: TemplateWrapper) -> None: '''Метод описывающий действия при append = "clear", если шаблон -- @@ -1010,7 +1037,8 @@ class TemplateExecutor: self._chmod_file(template_object.target_path, template_object.parameters.chmod) - template_object.add_to_contents() + if self.dbpkg: + template_object.add_to_contents() def _append_link_file(self, template_object: TemplateWrapper) -> None: '''Метод описывающий действия при append = "link", если шаблон -- @@ -1027,7 +1055,8 @@ class TemplateExecutor: self._chown_file(template_object.parameters.source, template_object.parameters.chown) - template_object.add_to_contents() + if self.dbpkg: + template_object.add_to_contents() def _create_directory(self, template_object: TemplateWrapper, path_to_create=None) -> None: @@ -1560,8 +1589,11 @@ class DirectoryTree: class DirectoryProcessor: '''Класс обработчика директорий шаблонов.''' def __init__(self, action: str, datavars_module=Variables(), package='', - output_module=IOModule()): - self.action = action + output_module=IOModule(), dbpkg=True): + if isinstance(action, list): + self.action = action + else: + self.action = [action] self.output = output_module self.datavars_module = datavars_module @@ -1603,12 +1635,12 @@ class DirectoryProcessor: # Инициализируем исполнительный модуль. self.template_executor = TemplateExecutor( - datavars_module=self.datavars_module, - chroot_path=self.cl_chroot_path, - cl_config_archive=self.cl_config_archive, - cl_config_path=self.cl_config_path, - execute_archive_path=self.cl_exec_dir_path - ) + datavars_module=self.datavars_module, + chroot_path=self.cl_chroot_path, + cl_config_archive=self.cl_config_archive, + cl_config_path=self.cl_config_path, + execute_archive_path=self.cl_exec_dir_path, + dbpkg=dbpkg) # Инициализируем шаблонизатор. self.template_engine = TemplateEngine( @@ -1633,8 +1665,18 @@ class DirectoryProcessor: return # Получаем список директорий шаблонов. - self.template_paths = (self.datavars_module. - main.cl_template_path.split(',')) + # TODO переменная список. + if isinstance(self.datavars_module, (Datavars, NamespaceNode)): + var_type = self.datavars_module.main[ + 'cl_template_path'].variable_type + else: + var_type = StringType + + if var_type is StringType: + self.template_paths = (self.datavars_module. + main.cl_template_path.split(',')) + elif var_type is ListType: + self.template_paths = self.datavars_module.main.cl_template_path # Список обработанных пакетов. self.processed_packages = [] @@ -1649,11 +1691,19 @@ class DirectoryProcessor: '''Метод для получения из соответствующей переменной списка паттернов для обнаружения игнорируемых в ходе обработки шаблонов файлов.''' if 'cl_ignore_files' in self.datavars_module.main: + if isinstance(self.datavars_module, (Datavars, NamespaceNode)): + var_type = self.datavars_module.main[ + 'cl_ignore_files'].variable_type + else: + var_type = StringType cl_ignore_files = self.datavars_module.main.cl_ignore_files cl_ignore_files_list = [] - for pattern in cl_ignore_files.split(','): - cl_ignore_files_list.append(pattern.strip()) + if var_type is StringType: + for pattern in cl_ignore_files.split(','): + cl_ignore_files_list.append(pattern.strip()) + elif var_type is ListType: + cl_ignore_files_list = cl_ignore_files return cl_ignore_files_list else: return [] @@ -1674,8 +1724,11 @@ class DirectoryProcessor: # быстрой обработки параметра merge. self.fill_trees = bool(self.for_package) if self.for_package: - package = Package(self.for_package, - chroot_path=self.cl_chroot_path) + if self.for_package is NonePackage: + package = self.for_package + else: + package = Package(self.for_package, + chroot_path=self.cl_chroot_path) else: package = None @@ -1714,9 +1767,10 @@ class DirectoryProcessor: if self.for_package not in self.packages_file_trees: self.output.set_error( - "Error: package '{0}' not found for action '{1}'.". - format(self.for_package, self.action) - ) + "Error: package '{0}' not found for action{1} '{2}'.". + format(self.for_package, + 's' if len(self.action) > 1 else '', + ', '.join(self.action))) not_merged_packages.append(self.for_package) continue @@ -1739,7 +1793,7 @@ class DirectoryProcessor: self.output.set_error('Packages {} is not merged.'. format(','.join(self.packages_to_merge))) else: - self.output.set_success('All packages are merged...') + self.output.set_success('All packages are merged.') def _run_exec_files(self): '''Метод для выполнения скриптов, полученных в результате обработки @@ -1810,7 +1864,7 @@ class DirectoryProcessor: directory_tree = {} return - directory_parameters.print_parameters_for_debug() + # directory_parameters.print_parameters_for_debug() # Корректируем путь к целевой директории. current_target_path = self._make_target_path(current_target_path, @@ -1909,7 +1963,7 @@ class DirectoryProcessor: if template_text is False: continue - template_parameters.print_parameters_for_debug() + # template_parameters.print_parameters_for_debug() # Если находимся на стадии заполнения дерева директорий -- # проверяем параметры package и action с заполнением дерева. @@ -1949,8 +2003,8 @@ class DirectoryProcessor: continue # * * * ПРИДУМАТЬ ОПТИМИЗАЦИЮ * * * - # Потому что накладывать дерево каждый раз, когда обнаружены файлы - # какого-то пакета не рационально. + # TODO Потому что накладывать дерево каждый раз, когда обнаружены + # файлы какого-то пакета не рационально. # Обновляем дерево директорий для данного пакета, если происходит # его заполнение. if self.fill_trees: @@ -2124,8 +2178,12 @@ class DirectoryProcessor: template_path)) return False - self.output.set_success('Processing directory: {}'. - format(template_path)) + if template_type == DIR: + self.output.set_success('Processed directory: {}'. + format(template_path)) + else: + self.output.set_success('Processed template: {}'. + format(template_path)) return target_path def _update_package_tree(self, package, current_level_tree): @@ -2160,29 +2218,28 @@ class DirectoryProcessor: ("Action parameter is not set for template:" " {0}").format(template_path)) return False - elif parameters.action != self.action: + elif parameters.action not in self.action: self.output.set_warning( ("Action parameter value '{0}' does not match its" - " current value '{1}'. Template: {2}").format( - parameters.action, - self.action, - template_path)) + " current value{1} '{2}'. Template: {3}").format( + parameters.action, + 's' if len(self.action) > 1 + else '', + ', '.join(self.action), + template_path)) return False if self.for_package: if not parameters.package: - self.output.set_warning( - "'package' parameter is not defined. Template: {}". - format(template_path)) - # пока что считаем наличие параметра package необязательным - # return False + if self.for_package is not NonePackage: + self.output.set_warning( + "'package' parameter is not defined. Template: {}". + format(template_path)) elif parameters.package != self.for_package: if directory_tree is not None: template_name = os.path.basename(template_path) directory_tree[template_name] = None - self.output.set_warning('DirectoryTree: {}'. - format(directory_tree)) self.output.set_warning( ("'package' parameter value '{0}' does not " diff --git a/calculate/utils/files.py b/calculate/utils/files.py index 81bf9e7..cb30c89 100644 --- a/calculate/utils/files.py +++ b/calculate/utils/files.py @@ -38,7 +38,7 @@ class KeyboardInputProcess(): return '' -class Process(): +class Process: '''Класс-обертка для работы с процессами.''' STDOUT = STDOUT PIPE = PIPE diff --git a/calculate/utils/io_module.py b/calculate/utils/io_module.py index 80e02e3..6940ff9 100644 --- a/calculate/utils/io_module.py +++ b/calculate/utils/io_module.py @@ -1,5 +1,6 @@ import sys import os +from contextlib import contextmanager class ColorPrint: @@ -172,3 +173,7 @@ class IOModule: if self.save_messages: self.messages.append(('fail', message)) self.console_output.print_not_ok(message) + + @contextmanager + def set_task(self, message): + pass diff --git a/calculate/utils/package.py b/calculate/utils/package.py index 8b5149e..5eddcf0 100644 --- a/calculate/utils/package.py +++ b/calculate/utils/package.py @@ -212,22 +212,32 @@ class PackageAtomName: @property def name(self): + if self._package_directory is None: + return 'None' return os.path.basename(self._package_directory) @property def category(self): + if self._package_directory is None: + return 'None' return os.path.basename(os.path.dirname(self._package_directory)) @property def atom(self): + if self._package_directory is None: + return 'None' return "{}/{}".format(self.category, self.name) @property def version(self): + if self._package_directory is None: + return 'None' return self._version @property def use_flags(self): + if self._package_directory is None: + return [] use_path = os.path.join(self._package_directory, 'USE') try: return read_file(use_path).strip('\n').split(' ') @@ -241,6 +251,8 @@ class PackageAtomName: @property def slot(self): + if self._package_directory is None: + return None slot_path = os.path.join(self._package_directory, 'SLOT') try: return read_file(slot_path).strip('\n') @@ -263,9 +275,13 @@ class PackageAtomName: return False def __bool__(self): + if self._package_directory is None: + return True return bool(self._package_directory) def __repr__(self): + if self._package_directory is None: + return '' return ''.format(self.category, self.name) @@ -273,6 +289,9 @@ class PackageAtomName: return hash(self._package_directory) +NonePackage = PackageAtomName({'pkg_path': None, 'version': None}) + + class PackageAtomParser: '''Класс для парсинга параметра package, его проверки, а также определения принадлежности файла пакету.''' diff --git a/calculate/variables/datavars.py b/calculate/variables/datavars.py index ba87ebc..3e642d8 100644 --- a/calculate/variables/datavars.py +++ b/calculate/variables/datavars.py @@ -1,8 +1,9 @@ # vim: fileencoding=utf-8 # +import re import ast import dis -from typing import List, Any +from typing import List, Any, Union from contextlib import contextmanager from inspect import signature, getsource from types import FunctionType, LambdaType @@ -37,6 +38,7 @@ class CyclicVariableError(VariableError): class VariableType: '''Базовый класс для типов.''' name = 'undefined' + python_type = None @classmethod def process_value(cls, value, variable): @@ -52,6 +54,7 @@ class IniType(VariableType): '''Класс, соответствующий типу переменных созданных в calculate.ini файлах. ''' name = 'ini' + python_type: type = str @classmethod def process_value(cls, value, variable): @@ -61,6 +64,7 @@ class IniType(VariableType): class StringType(VariableType): '''Класс, соответствующий типу переменных с строковым значением.''' name = 'string' + python_type: type = str @classmethod def process_value(cls, value, variable) -> str: @@ -79,6 +83,7 @@ class StringType(VariableType): class IntegerType(VariableType): '''Класс, соответствующий типу переменных с целочисленным значением.''' name = 'integer' + python_type: type = int @classmethod def process_value(cls, value, variable) -> int: @@ -97,6 +102,7 @@ class IntegerType(VariableType): class FloatType(VariableType): '''Класс, соответствующий типу переменных с вещественным значением.''' name = 'float' + python_type: type = float @classmethod def process_value(cls, value, variable) -> float: @@ -115,6 +121,7 @@ class FloatType(VariableType): class BooleanType(VariableType): '''Класс, соответствующий типу переменных с булевым значением.''' name = 'bool' + python_type: type = bool true_values = {'True', 'true'} false_values = {'False', 'false'} @@ -138,10 +145,10 @@ class BooleanType(VariableType): class ListType(VariableType): name = 'list' + python_type: type = list @classmethod def process_value(cls, value, variable) -> list: - # TODO нормально все сделать. if isinstance(value, list): return value elif isinstance(value, str): @@ -184,6 +191,17 @@ class HashValue: self = self.master_variable.get_value()[self.key] return self.value + def set(self, value) -> None: + '''Метод для задания одного значения хэша без изменения источника + значения.''' + current_hash = self.master_variable.get_value().get_hash() + current_hash[self.key] = value + self.master_variable.set(current_hash) + + def reset(self): + '''Метод для сброса значения, установленного поверх источника.''' + self.master_variable.reset() + class Hash: '''Класс реализующий контейнер для хранения хэша в переменной @@ -251,6 +269,7 @@ class Hash: class HashType(VariableType): '''Класс, соответствующий типу переменных хэшей.''' name = 'hash' + python_type: type = dict @classmethod def process_value(cls, values, variable) -> Hash: @@ -266,6 +285,8 @@ class HashType(VariableType): @classmethod def fixed(cls, variable_object) -> None: + '''Метод, который передается переменной вместо типа, если нужно задать + тип фиксированного хэша.''' variable_object.variable_type = cls variable_object.fixed = True @@ -292,13 +313,16 @@ class Table: type(row))) def get_table(self) -> List[dict]: + '''Метод для получения всего списка строк таблицы.''' return self._rows def add_row(self, row: dict): + '''Метод для добавления строк в таблицу.''' self._check_columns(row) self._rows.append(row) def change_row(self, row: dict, index: int) -> None: + '''Метод для замены существующей строки.''' self._check_columns(row) self._rows[index] = row @@ -340,6 +364,8 @@ class Table: class TableType(VariableType): name = 'table' + # TODO Сомнительно. + python_type: type = list @classmethod def process_value(self, value: List[dict], variable) -> Table: @@ -353,6 +379,16 @@ class TableType(VariableType): return Table(value, variable) +class Static: + '''Класс для указания в качестве аргументов зависимостей статичных + значений, а не только переменных.''' + def __init__(self, value): + self.value = value + + def get_value(self): + return self.value + + class VariableWrapper: '''Класс обертки для переменных, с помощью которого отслеживается применение переменной в образовании значения переменной от нее зависящей. @@ -365,7 +401,8 @@ class VariableWrapper: def value(self): '''Метод возвращающий значение переменной и при этом добавляющий его в подписки.''' - self._subscriptions.add(self._variable) + if not isinstance(self._variable, Static): + self._subscriptions.add(self._variable) value = self._variable.get_value() if isinstance(value, Hash): value = value.get_hash() @@ -429,17 +466,23 @@ class DependenceSource: содержащего переменные и строки, в список аргументов состоящий только из нод переменных и значений хэшей.''' if not self._args_founded: - for index in range(0, len(self._args)): - if isinstance(self._args[index], str): - variable = Dependence._find_variable( - self._args[index], - current_namespace=namespace) - if variable is None: - raise DependenceError("variable '{}' not found". - format(variable)) - self._args[index] = variable + self._args = self.find_variables(self._args, namespace) self._args_founded = True + def find_variables(self, variables, namespace): + '''Метод для поиска переменных по заданным путям к ним.''' + output = [] + for variable in variables: + if isinstance(variable, str): + variable = Dependence._get_variable( + variable, + current_namespace=namespace) + if variable is None: + raise DependenceError("variable '{}' not found". + format(variable)) + output.append(variable) + return output + @property def subscriptions(self) -> set: return self._subscriptions @@ -460,12 +503,18 @@ class DependenceSource: # Проверяем совпадение количества аргументов функции и заданных для # функции переменных. + self.check_signature(function_to_check, self._args) + + @staticmethod + def check_signature(function_to_check, arguments): + '''Метод для проверки соответствия сигнатуры функции и заданного для + нее набора аргументов.''' function_signature = signature(function_to_check) - if not len(self._args) == len(function_signature.parameters): - raise VariableError("the depend function takes {} arguments," - " while {} is given".format( + if not len(arguments) == len(function_signature.parameters): + raise DependenceError("the depend function takes {} arguments," + " while {} is given".format( len(function_signature.parameters), - len(self._args))) + len(arguments))) def __ne__(self, other) -> bool: if not isinstance(other, DependenceSource): @@ -528,6 +577,11 @@ class VariableNode: self.value = None self._invalidated = True + # Флаг имеющий значение только для переменных типа HashType. + # Предназначен для включения проверки соответствия полей хэша при + # установке значения. + self._fixed = False + # Источник значения переменной, может быть значением, а может быть # зависимостью. self._source = source @@ -539,10 +593,6 @@ class VariableNode: self.set_by_user = False self._readonly = False - # Флаг имеющий значение только для переменных типа HashType. - # Предназначен для включения проверки соответствия полей хэша при - # установке значения. - self._fixed = False def update_value(self) -> None: '''Метод для обновления значения переменной с помощью указанного @@ -579,7 +629,8 @@ class VariableNode: self.value = self.variable_type.process_value(value, self) self._invalidated = False - def set_variable_type(self, variable_type, readonly=None, fixed=None): + def set_variable_type(self, variable_type: VariableType, + readonly: str = None, fixed: str = None) -> None: '''Метод для установки типа переменной.''' if readonly is not None and isinstance(readonly, bool): self._readonly = readonly @@ -604,9 +655,8 @@ class VariableNode: self._invalidated = False self.set_by_user = False - self._invalidate() - self.set_by_user = True self.value = self.variable_type.process_value(value, self) + self._invalidate(set_by_user=True) def reset(self): '''Метод для сброса пользовательского значения.''' @@ -621,10 +671,6 @@ class VariableNode: @source.setter def source(self, source) -> None: - if self._readonly: - raise VariableError("can not change the variable '{}': read only". - format(self.get_fullname())) - # Если источники не совпадают или текущее значение переменной было # установлено пользователем, то инвалидируем переменную и меняем # источник. @@ -653,21 +699,22 @@ class VariableNode: def fixed(self, value) -> bool: self._fixed = value - def _invalidate(self) -> None: + def _invalidate(self, set_by_user=False) -> None: '''Метод для инвалидации данной переменной и всех зависящих от нее переменных.''' - # print('{} is invalidated'.format(self.get_fullname())) if not self._invalidated and not self.set_by_user: - if self.variable_type is not HashType: - self.value = None self._invalidated = True + self.set_by_user = set_by_user + for subscriber in self.subscribers: subscriber._invalidate() @contextmanager def _start_calculate(self): '''Менеджер контекста устанавливающий флаг, указывающий, что данная - переменная в состоянии расчета.''' + переменная в состоянии расчета. В данном случае необходима только для + того, чтобы при получении значения параметра внутри метода для расчета + не иницировалось ее обновление, которое может привести к рекурсии.''' try: self.calculating = True yield self @@ -676,6 +723,8 @@ class VariableNode: def get_value(self) -> Any: '''Метод для получения значения переменной.''' + if self._invalidated and self._source is None and self._value is None: + return None if self._invalidated and not self.set_by_user: self.update_value() return self.value @@ -747,6 +796,12 @@ class NamespaceNode: else: return self.name + def get_package_name(self) -> str: + if self.parent.name == '': + return self.name + else: + return self.parent.get_package_name() + def __getattr__(self, name: str): '''Метод возвращает ноду пространства имен или значение переменной.''' if name in self.namespaces: @@ -790,41 +845,39 @@ class DependenceAPI(metaclass=Singleton): def __call__(self, *variables, depend=None): subscriptions = list() for variable in variables: - # Поиск переменных теперь происходит при обновлении значения - # переменной. - - # if isinstance(variable, str): - # variable = self._find_variable(variable) - # if variable is None: - # raise DependenceError("variable '{}' not found".format( - # variable)) - # elif not isinstance(variable, VariableNode): - # raise DependenceError("dependence variables must be 'str' or" - # " 'VariableNode' not '{}'".format( - # type(variable))) if not (isinstance(variable, str) or - isinstance(variable, VariableNode)): - raise DependenceError("dependence variables must be 'str' or" - " 'VariableNode' not '{}'".format( - type(variable))) + isinstance(variable, VariableNode) or + isinstance(variable, Static)): + variable = Static(variable) subscriptions.append(variable) return DependenceSource(subscriptions, depend=depend) - def _find_variable(self, variable_name, current_namespace=None): + def _get_variable(self, variable_name, current_namespace=None): '''Метод для поиска переменной в пространствах имен.''' if current_namespace is None: current_namespace = self.current_namespace + return self.find_variable(variable_name, self.datavars_root, + current_namespace=current_namespace) + + @staticmethod + def find_variable(variable_name, datavars_root, current_namespace=None): + '''Метод для поиска переменных по строковому пути от корня переменных + или от текущего пространства имен.''' name_parts = variable_name.split('.') if not name_parts[0]: - namespace = current_namespace - for index in range(1, len(name_parts)): - if not name_parts[index]: - namespace = namespace.parent - else: - name_parts = name_parts[index:] - break + if current_namespace is not None: + namespace = current_namespace + for index in range(1, len(name_parts)): + if not name_parts[index]: + namespace = namespace.parent + else: + name_parts = name_parts[index:] + break + else: + raise VariableNotFoundError( + f"variable '{variable_name}' is not found.") else: - namespace = self.datavars_root + namespace = datavars_root search_result = namespace for part in name_parts: @@ -832,6 +885,44 @@ class DependenceAPI(metaclass=Singleton): return search_result +class CopyAPI(metaclass=Singleton): + '''Класс для создания зависимостей представляющих собой простое копирование + значения переменной в зависимую переменную.''' + def __call__(self, variable: Union[str, VariableNode]): + return Dependence(variable) + + +class FormatAPI(metaclass=Singleton): + '''Класс для создания зависимостей представляющих собой форматируемую + строку с указанием переменных в тех местах, где их значения должны быть + подставлены в строку.''' + pattern = re.compile( + r'{\s*([a-zA-Z][a-zA-Z_0-9]+)?(.[a-zA-Z][a-zA-Z_0-9]+)+\s*}') + + def __call__(self, string: str): + vars_list = [] + + def subfunc(matchobj): + vars_list.append(matchobj.group(0)[1:-1].strip()) + return '{}' + format_string = self.pattern.sub(subfunc, string) + + def depend_function(*args): + values = [arg.value for arg in args] + return format_string.format(*values) + + return Dependence(*vars_list, depend=depend_function) + + +class CalculateAPI(metaclass=Singleton): + '''Метод для создания зависимостей, представляющих собой функцию для + вычисления значения зависимой переменной на основе значений указанных + переменных.''' + def __call__(self, *args): + depend_function = args[0] + return Dependence(*args[1:], depend=depend_function) + + class VariableAPI(metaclass=Singleton): '''Класс для создания переменных при задании их через python-скрипты.''' @@ -943,5 +1034,9 @@ class NamespaceAPI(metaclass=Singleton): Dependence = DependenceAPI() +Copy = CopyAPI() +Format = FormatAPI() +Calculate = CalculateAPI() + Variable = VariableAPI() Namespace = NamespaceAPI(Variable, Dependence) diff --git a/calculate/variables/loader.py b/calculate/variables/loader.py index 9b541b2..58948ae 100644 --- a/calculate/variables/loader.py +++ b/calculate/variables/loader.py @@ -8,7 +8,7 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\ ListType, IntegerType,\ FloatType, IniType, TableType,\ Namespace, HashType,\ - VariableNotFoundError + VariableNotFoundError, VariableError from calculate.utils.gentoo import ProfileWalker from calculate.utils.files import read_file, FilesError from calculate.utils.tools import Singleton @@ -197,7 +197,6 @@ class NamespaceIniFiller: for section in sections: if isinstance(self.current_namespace, Datavars): if section not in self.current_namespace: - # TODO Поменять на логгирование. self._set_error(lineno, 'VariableError', "variables package '{}' is not found.". format(section)) @@ -216,7 +215,6 @@ class NamespaceIniFiller: self.current_namespace.add_namespace( NamespaceNode(section)) else: - # TODO Поменять на логгирование. self._set_error(lineno, 'VariableError', "can not create namespace '{}.{}' in" " not 'custom' namespace.".format( @@ -243,7 +241,7 @@ class NamespaceIniFiller: " package '{}' is not found.".format( ".".join(sections), section)) - return + return elif isinstance(current_namespace, NamespaceNode): if section in current_namespace.namespaces: current_namespace = current_namespace[section] @@ -261,6 +259,7 @@ class NamespaceIniFiller: " is not found.".format( ".".join(sections))) return + if not self.modify_only: current_namespace.clear() else: @@ -325,21 +324,26 @@ class NamespaceIniFiller: if key not in self.current_namespace: self.define_variable(key, value, lineno) else: - self.change_value(key, value) + self.change_value(key, value, lineno) elif optype == Define.append: if key not in self.current_namespace: self.define_variable(key, value, lineno) else: - self.append_value(key, value) + self.append_value(key, value, lineno) elif optype == Define.remove: if key not in self.current_namespace: self.define_variable(key, value, lineno) else: - self.remove_value(key, value) + self.remove_value(key, value, lineno) - def change_value(self, key: str, value: str) -> None: + def change_value(self, key: str, value: str, lineno) -> None: '''Метод для изменения значения переменной.''' variable = self.current_namespace[key] + if variable.readonly: + self._set_error(lineno, 'VariableError', + "can not change readonly variable " + f"'{self.current_namespace.get_fullname()}.{key}'") + return variable.source = value def define_variable(self, key: str, value: str, lineno) -> None: @@ -354,9 +358,14 @@ class NamespaceIniFiller: self.current_namespace.get_fullname(), key)) - def append_value(self, key: str, value: str) -> None: + def append_value(self, key: str, value: str, lineno) -> None: '''Метод выполняющий действия возложенные на оператор +=.''' variable = self.current_namespace[key] + if variable.readonly: + self._set_error(lineno, 'VariableError', + "can not change readonly variable " + f"'{self.current_namespace.get_fullname()}.{key}'") + return variable_value = variable.get_value() if variable.variable_type is IniType: @@ -383,10 +392,16 @@ class NamespaceIniFiller: variable.source = variable_value - def remove_value(self, key: str, value: str) -> None: + def remove_value(self, key: str, value: str, lineno) -> None: '''Метод выполняющий действия возложенные на оператор -=.''' variable = self.current_namespace[key] + if variable.readonly: + self._set_error(lineno, 'VariableError', + "can not change readonly variable " + f"'{self.current_namespace.get_fullname()}.{key}'") + return variable_value = variable.get_value() + if variable.variable_type is IniType: value_list = value.split(',') variable_list = [item.strip() for item in @@ -413,6 +428,11 @@ class NamespaceIniFiller: def update_hash(self, key: str, value: str, optype, lineno): '''Метод для изменения переменных хэшей через calculate.ini.''' + if self.current_namespace.readonly: + self._set_error(lineno, 'VariableError', + "can not change readonly hash variable " + f"'{self.current_namespace.get_fullname()}'") + return hash_to_update = self.current_namespace.get_value().get_hash() if key not in hash_to_update: # Если ключ отсутствует в хэше, то проверяем, является ли он @@ -515,6 +535,8 @@ class VariableLoader: " variables.") return + self.output.set_info("Load variables from profile: '{}'.".format( + profile_path)) self._fill_from_profile_ini(profile_path) def load_user_variables(self): @@ -592,8 +614,6 @@ class VariableLoader: def fill_from_custom_ini(self, file_path: str): '''Метод для заполнения переменных из конкретного указанного файла.''' - self.output.set_info('Loading variables from file: {}'.format( - file_path)) if os.path.exists(file_path): ini_file_text = read_file(file_path) self.ini_filler.fill(self.datavars, ini_file_text) @@ -698,6 +718,8 @@ class Datavars: self._loader.load_from_profile() self._loader.load_user_variables() + # Создаем словарь переменных, которые нужно сохранить потом в + # ini-файлах. try: self.variables_to_save = {target: dict() for target in self.system.env_order if target in @@ -725,6 +747,15 @@ class Datavars: available_packages.update({file_name: file_path}) return available_packages + def _load_package(self, package_name): + '''Метод для загрузки переменных содержащихся в указанном пакете.''' + self.output.set_info("Loading package '{}'".format(package_name)) + try: + self._loader.load_variables_package(package_name) + except Exception as error: + raise VariableError("Can not load variables package: {}". + format(error)) + def __getattr__(self, package_name: str): '''Метод возвращает ноду пространства имен, соответствующего искомому пакету.''' @@ -738,7 +769,7 @@ class Datavars: raise VariableNotFoundError("variables package '{}' is not found". format(package_name)) else: - self._loader.load_variables_package(package_name) + self._load_package(package_name) return self.root[package_name] def __getitem__(self, package_name: str) -> None: @@ -750,11 +781,14 @@ class Datavars: custom_namespace = NamespaceNode('custom') self.root.add_namespace(custom_namespace) return self.root[package_name] + elif package_name == 'tasks': + self.create_tasks() + return self.root[package_name] elif package_name not in self._available_packages: raise VariableNotFoundError("variables package '{}' is not found". format(package_name)) else: - self._loader.load_variables_package(package_name) + self._load_package(package_name) return self.root[package_name] def __contains__(self, package_name): @@ -764,16 +798,30 @@ class Datavars: custom_namespace = NamespaceNode('custom') self.root.add_namespace(custom_namespace) return True + elif package_name == 'tasks': + self.create_tasks() + return True elif (package_name not in self._available_packages and package_name != 'custom'): return False else: - self._loader.load_variables_package(package_name) + self._load_package(package_name) return True def add_namespace(self, namespace_node): self.root.add_namespace(namespace_node) + def create_tasks(self): + '''Метод для создания всех необходимых пространств имен для работы + задач.''' + tasks = NamespaceNode('tasks') + self.add_namespace(tasks) + + env = NamespaceNode('env') + tasks.add_namespace(env) + + env.add_namespace('loop') + @property def namespaces(self): return self.root.namespaces diff --git a/calculate/variables/old_vars/datavars.py b/calculate/variables/old_vars/datavars.py deleted file mode 100644 index 8b57869..0000000 --- a/calculate/variables/old_vars/datavars.py +++ /dev/null @@ -1,575 +0,0 @@ -# import site -# import os -import re -# import sys -import types -# import functools -from contextlib import contextmanager - -_ = lambda x: x - - -class BaseClass: - BASE_CLASS = "BaseClass" - - @classmethod - def is_implementation(cls, check_class): - '''Метод для проверки того, что класс является производным базового - класса, а не самим базовым. Используется в автозагрузке переменных из - модулей.''' - if isinstance(check_class, type) and issubclass(check_class, cls): - if check_class.BASE_CLASS == cls.BASE_CLASS and \ - check_class.__name__ != cls.BASE_CLASS: - return True - return False - - -class VariableError(Exception): - pass - - -class VariableNotFoundError(VariableError): - pass - - -class CyclicVariableError(VariableError): - def __init__(self, *queue): - self.queue = queue - - def __str__(self): - return _("Cyclic dependence in variables: {}").format(", ".join( - self.queue[:-1])) - - -class VariableType: - '''Базовый класс для объектов свойств.''' - def __init__(self, parent): - self.parent = parent - - -class StringVariable(VariableType): - '''Класс свойства, соответствующий переменным просто хранящим строки как - значения.''' - pass - - -class ListVariable(VariableType): - '''Класс свойства, соответствующий переменным хранящим списки значений.''' - def set_value(self, value, force=False): - if isinstance(value, (list, tuple)): - return value - elif isinstance(value, str): - return value.split(",") - else: - raise VariableError( - _("The value of variable '{varname}' must be list").format( - varname=self.parent.name)) - - def post_get(self, value): - return value - - -class ReadonlyVariable(VariableType): - '''Класс свойства, соответствующий переменным только для чтения.''' - def set_value(self, value, force=False): - if not force: - raise VariableError( - _("Attempting to rewrite readonly variable {}").format( - self.parent.name)) - return value - - -class IntegerVariable(VariableType): - '''Класс свойства, соответствующий целочисленным переменным.''' - re_match = re.compile(r"^-?\d+$") - - def check(self, value): - if value and not self.re_match.match(value): - raise VariableError( - _("The value of variable '{varname}' must be integer").format( - varname=self.parent.name)) - - def post_get(self, value): - return int(value) - - -class BooleanVariable(VariableType): - '''Класс свойства, соответствующий логическим переменным.''' - def post_get(self, value): - return value == "true" - - -class ChoiceVariable(VariableType): - '''Класс свойства, соответствующий переменным выбора.''' - def check(self, value): - choices = self.parent.choice() - if value and value not in choices: - raise VariableError( - _("The value for variable '{varname}' may be " - "{vartype}").format(varname=self.parent.name, - vartype=",".join(choices))) - - def choice(self): - if self.parent.__class__.choice == Variable.choice and \ - self.parent.__class__.choice_comment == Variable.choice_comment: - raise VariableError(_("Wrong choice variable {}").format( - self.parent.name)) - return [x[0] for x in self.parent.choice_comment()] - - def choice_comment(self): - return [(x, x) for x in self.parent.choice()] - - -class DefaultValue(VariableType): - '''Класс свойства, соответствующий логическим переменным.''' - def __init__(self, value): - self.value = value - self.parent = None - - def get_value(self, subscribers=None): - if self.parent._value is None: - self.parent._unsubscribe_depends() - self.parent._value = self.value - - -class IniCreated(DefaultValue): - '''Класс свойства, соответствующий переменным, полученным из ini-файла.''' - pass - - -class Variable(BaseClass): - BASE_CLASS = "Variable" - - On = "on" - Off = "off" - value = "" - - class Type: - Bool = "bool" - List = "list" - String = "string" - Integer = "int" - Choice = "choice" - - properties = (StringVariable,) - - def __init__(self, name): - self.name = name - self._value = None - self.vars = None - self.subscribers = set() - - # множество переменных, на которые подписана данная переменная. - self.subscriptions = set() - self.calculating = False - self._properties = [x(self) for x in self.properties] - # версия значения переменной, если переменная со времен получения - # значения успела обновиться, исключить сброс ее значения - - @property - def fullname(self): - '''Метод для получения полного имени переменной.''' - return "{}.{}".format(self.vars.get_fullname(), self.name) - - def add_property(self, prop): - '''Метод для подключения свойств к переменной.''' - prop.parent = self - self._properties.insert(0, prop) - - def find_property(self, property_class): - '''Метод для проверки наличия заданного свойства у переменной.''' - for _property in self._properties: - if isinstance(_property, property_class): - return _property - else: - return None - - def _emit_invalidate(self, subscribers): - print('subscribers = {}'.format(subscribers)) - for f in subscribers: - print('f = {}'.format(f)) - f() - - def _unsubscribe_depends(self): - for subscription in self.subscriptions: - subscription.update_unsubscribe(self.invalidate) - self.subscriptions = set() - - def update_subscribe(self, f_or_var): - if isinstance(f_or_var, Variable): - f_or_var.subscriptions.add(self) - self.subscribers.add(f_or_var.invalidate) - else: - self.subscribers.add(f_or_var) - - def update_unsubscribe(self, f): - if f in self.subscribers: - self.subscribers.remove(f) - - def invalidate(self): - '''Метод для инвалидации всех переменных-подписчиков.''' - print('invalidation method') - print('subscribers = {}'.format(self.subscribers)) - print('subscriptions = {}'.format(self.subscriptions)) - self._value = None - if self.subscribers: - subscribers = self.subscribers - self.subscribers = set() - self._emit_invalidate(subscribers) - - def set_parent(self, namespace): - '''Метод для установки родительского пространства имен переменной.''' - self.vars = namespace - - @contextmanager - def _start_calculate(self): - '''Менеджер контекста для запуска расчета переменных с учетом всех - взаимозависимостей.''' - try: - self.calculating = True - yield self - finally: - self.calculating = False - - def get_value(self, subscription=None): - for get_method in self.call_properties("get_value"): - get_method(subscription) - - if self.calculating: - raise CyclicVariableError(self.name) - - if self._value is None: - with self._start_calculate(): - try: - self._unsubscribe_depends() - self._value = self.get() - - if isinstance(self._value, types.GeneratorType): - self._value = list(self._value) - except CyclicVariableError as error: - raise CyclicVariableError(self.name, *error.queue) - - if subscription is not None: - self.update_subscribe(subscription) - return self.post_get(self._value) - - def post_get(self, value): - for post_get_method in self.call_properties("post_get"): - result = post_get_method(value) - if result is not None: - return result - return value - - def call_properties(self, method_name, *args): - '''Метод для вызова указанного метода у всех объектов, которыми владеет - переменная.''' - for _property in self._properties: - property_method = getattr(_property, method_name, None) - if property_method: - yield property_method - - def set_value(self, value, force=False): - '''Метод для установки некоторого заданного значения всем объектам, - принадлежащим переменной.''' - for setter in self.call_properties("set_value"): - value = setter(value, force) - - value = self.set(value) - self.check(value) - self.invalidate() - self._value = value - self._unsubscribe_depends() - - def check(self, value): - '''Метод для проверки корректности устанавливаемого значения путем - вызова проверочных методов всех объектов, которыми владеет переменная. - ''' - for checker in self.call_properties("check"): - checker(value) - - def get(self): - '''Метод для заполнения переменной.''' - return self.value - - def get_comment_value(self, subscription=None): - '''Этот метод вызывается внутри методов get.''' - val = self.get_comment() - if subscription is not None: - self.update_subscribe(subscription) - return val - - def get_comment(self): - '''Метод для установки .''' - for get_method in self.call_properties("get_comment"): - return get_method() - return self.get_value() - - def set(self, value): - '''Метод для модификации переменной.''' - return value - - def choice(self): - '''Метод возвращет список доступных значений для переменной.''' - for f in self.call_properties("choice"): - return f() - return [] - - def choice_comment(self): - for f in self.call_properties("choice_comment"): - return f() - return [] - - -class NamespaceError(Exception): - pass - - -class Namespace(BaseClass): - '''Класс пространства имен.''' - BASE_CLASS = "Namespace" - - def __init__(self, name="", parent=None): - self._name = name - self.variables = {} - self.childs = {} - self.parent = parent or self - self.root = self - self._next_namespace = 0 - - def get_fullname(self): - '''Метод для получения полного имени пространства имен, включающего в - себя имена всех родительских пространств имен.''' - if self.parent is not self and self.parent.parent is not self.parent: - return "{}.{}".format(self.parent.get_fullname(), self._name) - else: - return self._name - - def __getattr__(self, name): - if name in self.childs: - return self.childs[name] - elif name in self.variables: - return self.variables[name] - else: - raise VariableNotFoundError( - _("Variable or namespace {varname} not found").format( - varname="{}.{}".format(self.get_fullname(), - name))) - - def clear_childs(self): - '''Метод для глубокой очистки пространства имен от всех дочерних - пространств имен.''' - for child in self.childs.values(): - child.clear_childs() - self.childs = {} - - def __getitem__(self, name): - return getattr(self, str(name)) - - def __setitem__(self, name, value): - return getattr(self, str(name)).set_value(value) - - def __iter__(self): - # Сортировка: вначале числовые ключи потом прочие. - def sortkey(x): - k, v = x - if k.isdigit(): - return (0, int(k), k) - else: - return (1, 0, k) - for k, v in sorted(self.childs.items(), key=sortkey): - yield v - - def __contains__(self, name): - return name in self.childs or name in self.variables - - def add_string_variable(self, varname: str, value: str): - '''Метод для добавления переменной с помощью строк.''' - var = Variable(varname) - var.value = value - var.set_parent(self) - self.variables[varname] = var - - def add_variable(self, variable: Variable): - '''Метод для добавления переменной.''' - self.variables[variable.name] = variable - variable.set_parent(self) - - def _get_next_namespace_name(self): - '''Метод для получения имени следующего по счету пространства имен.''' - name = str(self._next_namespace) - while name in self.childs: - self._next_namespace += 1 - name = str(self._next_namespace) - return name - - def add_namespace(self, namespace=None, name=None): - '''Метод для добавления пространств имен.''' - if name is None: - if namespace is None: - name = self._get_next_namespace_name() - else: - name = namespace._name - - if namespace is None: - namespace = Namespace(name) - - self.childs[name] = namespace - namespace.parent = self - namespace.root = self.root - - return namespace - - -class HashVariable(Namespace): - '''Класс переменных, представляющих собой словарь.''' - BASE_CLASS = "HashVariable" - - class HashValue(Variable): - BASE_CLASS = "HashValue" - - def __init__(self, name, master_variable): - super().__init__(name) - self.parent = None - self.master_variable = master_variable - - def get_value(self, subscription=None): - return self.master_variable.getHashValue(self.name, subscription) - - def set_value(self, value, force=False): - return self.master_variable.setHashValue(self.name, value, force) - - def invalidate(self): - self.master_variable.invalidate() - - class Data(Variable): - BASE_CLASS = "Data" - - def getHashValue(self, name, subscription=None): - return self.get_value(subscription)[name] - - def setHashValue(self, name, value, force): - if name in self.readonly_vars and not force: - raise VariableError( - _("Attempting to rewrite readonly variable {}"). - format(name)) - data = self.get_value().copy() - data[name] = value - self.set_value(data, force) - - def get_value(self, subscription=None): - return self.master_variable.get_value(subscription) - - hash_vars = [] - readonly_vars = [] - - def __init__(self, name, parent=None, hash_vars=None): - super().__init__(name) - if hash_vars is not None: - self.hash_vars = hash_vars - - self.parent = parent - if not self.hash_vars: - raise VariableError( - _("Missed '{attrname}' attribute for hash variable {varname}"). - format(attrname="hash_vars", varname=self.get_fullname())) - self.master_variable = self.Data(name) - self.master_variable.set_parent(parent) - self.master_variable.readonly_vars = self.readonly_vars - for varname in self.hash_vars: - var = self.HashValue(varname, self.master_variable) - self.add_variable(var) - - def invalidate(self): - self.master_variable.invalidate() - - -class TableVariable(Namespace): - """ - Переменная представляет собой список словарей - """ - BASE_CLASS = "TableVariable" - - class TableHashVariable(HashVariable): - BASE_CLASS = "TableHashVariable" - - def __init__(self, name, parent=None, hash_vars=None, - master_variable=None, index=None): - super().__init__(name, parent, hash_vars) - self.master_variable._index = index - - class Data(HashVariable.Data): - def getHashValue(self, name, subscription=None): - return self.vars.master_variable.getTableValue(name, - self._index, - subscription) - - def setHashValue(self, name, value, force): - self.vars.master_variable.setTableValue(name, self._index, - value, force) - - class Data(Variable): - BASE_CLASS = "Data" - - def getTableValue(self, name, index, subscription=None): - data = self.get_value(subscription) - return data[index][name] - - def setTableValue(self, name, index, value, force): - if name in self.readonly_vars and not force: - raise VariableError( - _("Attempting to rewrite readonly variable {}").format( - name)) - data = [x.copy() for x in self.get_value()] - rowdata = data[index] - rowdata[name] = value - self.set_value(data, force) - - @property - def childs(self): - if self._childs is None: - value = self.master_variable.get_value() - self._childs = {} - for i, row in enumerate(value): - hashvar = self.TableHashVariable(self.master_variable.name, - self, self.hash_vars, - self.master_variable, i) - self._childs[str(i)] = hashvar - return self._childs - - @childs.setter - def childs(self, value): - self._childs = value - - def get_value(self, subscription=None): - return self.master_variable.get_value(subscription) - - def set_value(self, value, force=False): - self.master_variable.set_value(value, force) - - def invalidate(self): - self.master_variable.invalidate() - - hash_vars = [] - readonly_vars = [] - - def _drop_childs(self): - self._childs = None - self.master_variable.update_subscribe(self._drop_childs) - - def clear_childs(self): - super().clear_childs() - self._drop_childs() - - def __init__(self, name, parent=None): - super().__init__(name) - self._childs = None - self.parent = parent - if not self.hash_vars: - raise VariableError( - _("Missed '{attrname}' attribute for table variable {varname}"). - format(attrname="hash_vars", - varname=self.get_fullname())) - self.master_variable = self.Data(name) - self.master_variable.set_parent(parent) - self.master_variable.readonly_vars = self.readonly_vars - self.master_variable.update_subscribe(self._drop_childs) diff --git a/calculate/variables/old_vars/main/__init__.py b/calculate/variables/old_vars/main/__init__.py deleted file mode 100644 index 820eb0c..0000000 --- a/calculate/variables/old_vars/main/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from calculate.vars.datavars import Variable, ReadonlyVariable - -class Chroot(Variable): - properties = [ReadonlyVariable] - value = "/" diff --git a/calculate/variables/old_vars/os/gentoo/__init__.py b/calculate/variables/old_vars/os/gentoo/__init__.py deleted file mode 100644 index e3dfe00..0000000 --- a/calculate/variables/old_vars/os/gentoo/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -from calculate.vars.datavars import Variable, ChoiceVariable, HashVariable, TableVariable, Namespace -from calculate.utils.files import stderr_devnull -import calculate.utils.fs as fs -import os - -""" -gentoo - make_profile - profile.path - profile.name - repositories[*].name - repositories[*].path - config -""" - -class MakeProfile(Variable): - """ - Путь до файла, указывающего на активный профиль - """ - value = '/etc/portage/make.profile' - -class Profile(Namespace): - """ - Параметры текущего профиля - """ - class Path(Variable): - """ - Абсолютный путь до профиля - """ - def get(self): - make_profile = self.vars.parent.make_profile.getValue(self) - make_profile_dir = os.path.dirname(make_profile) - profileLink = fs.readlink(make_profile) - if profileLink: - profileLink = os.path.normpath( - os.path.join(make_profile_dir,profileLink)) - return profileLink - else: - return "" - - class Name(Variable): - """ - Название профиля - """ - def get(self): - profile_path = self.vars.path.getValue(self) - if not profile_path: - return "" - repositories = self.vars.parent.repositories - for rep in repositories: - reppath = rep.path.getValue(self) - repname = rep.name.getValue(self) - removepart = os.path.normpath(os.path.join(reppath,"profiles")) - if profile_path.startswith(removepart): - return "%s:%s"%( - repname, - profile_path[len(removepart)+1:]) - return profile_path - -class Repositories(TableVariable): - """ - Информация о репозиториях - - name: имя репозитория - path: полный путь до репозитория - """ - class Data(TableVariable.Data): - def get(self): - config = self.vars.config.getValue(self) - return [ - { - 'name': name, - 'path': path - } - for path, name in config.repositories.location_map.items() - ] - hash_vars = ["name", "path"] - -class Config(Variable): - """ - Объект текущей конфигурации Portage - """ - def get(self): - from portage.package.ebuild.config import config - chroot_path = self.vars.root.main.chroot.getValue(self) - if chroot_path == '/': - with stderr_devnull(): - return config() diff --git a/calculate/variables/old_vars/vars_loader.py b/calculate/variables/old_vars/vars_loader.py deleted file mode 100644 index 1edc710..0000000 --- a/calculate/variables/old_vars/vars_loader.py +++ /dev/null @@ -1,335 +0,0 @@ -import re -import sys -import os -import importlib -import importlib.util -import site -from calculate.variables.old_vars.datavars import Variable, Namespace,\ - HashVariable, TableVariable,\ - IniCreated, DefaultValue -from calculate.utils.gentoo import ProfileWalker -from calculate.utils.fs import readFile -from calculate.utils.files import list_directory -from pyparsing import Literal, Word, ZeroOrMore, Group, Dict, Optional,\ - restOfLine, empty, printables, OneOrMore, oneOf, nums,\ - lineno, line, col, Keyword, SkipTo, LineEnd, Combine -from enum import Enum - - -class Define(Enum): - assign = 0 - append = 1 - remove = 2 - - -class CalculateIniParser: - '''Класс парсера calculate.ini файлов.''' - - def __init__(self): - self._errors = [] - - self.operations = {"=": Define.assign, - "+=": Define.append, - "-=": Define.remove} - - lbrack = Literal("[") - rbrack = Literal("]") - - # comma = Literal(",").suppress() - comment_symbol = Literal(';') | Literal('#') - - # Define = self.Define - value_operation = (Literal("=") | Combine(Literal("+") + Literal("=")) - | Combine(Literal("-") + Literal("="))) - - comment = comment_symbol + Optional(restOfLine) - - section_name = Word(printables+'\t', excludeChars='[]') - - value_name = Word(printables+'\t', excludeChars='=-+') - - # non_comma = Word(printables+'\t', excludeChars=',') - clear_section = lbrack.suppress() + Group(empty) + rbrack.suppress() - - section_start = Group(OneOrMore(lbrack.suppress() + section_name - + rbrack.suppress()) - + (clear_section | ~lbrack()) - + LineEnd().suppress()) - - # Если содержимое ini-файла не предваряется заголовком секции, - # значит эта строка ошибочна. - unexpected = Group(~section_start + SkipTo(LineEnd(), - include=True))("error") - unexpected.setParseAction(self._unexpected_token) - - key_value = (~lbrack + value_name - + value_operation + empty - + restOfLine + LineEnd().suppress()) - - def strip_key_value(tokens): - tokens[0] = tokens[0].strip() - tokens[1] = tokens[1].strip() - - key_value.setParseAction(strip_key_value) - - self.ini_section_parser = (section_start - + Group(ZeroOrMore( - Group(key_value | unexpected))) - | unexpected) - self.ini_section_parser.ignore(comment) - - def _unexpected_token(self, string, location, tokens): - '''Метод вызываемый парсером, если обнаружена некорректная строка, - предназначен для получения некорректной строки и ее дальнейшего - разбора.''' - print('UNEXPECTED TOKEN') - error_line = line(location, string).strip() - if error_line: - self._errors.append((error_line, lineno(location, string), - col(location, string))) - - @property - def errors(self): - errors = self._errors - self._errors = [] - return errors - - def parse(self, data): - for tokens, start, end in self.ini_section_parser.scanString(data): - if tokens.getName() == "error": - continue - - section, defkeys = tokens - - section_list = section.asList() - if section_list[-1] == []: - yield {'clear_section': (section_list[:-1], )} - else: - yield {'start_section': (section.asList(), )} - - for defkey in defkeys: - if defkey.getName() == "error": - continue - yield {'define_key': (section.asList(), defkey[0], defkey[2], - self.operations[defkey[1]])} - - -class NamespaceIniFiller: - '''Класс, предназначенный для наполнения Namespace объекта переменными - из calculate.ini файла.''' - def __init__(self): - self.ini_parser = CalculateIniParser() - - def error(self, lineno, error_message): - self.errors.append(lineno, error_message) - - def fill(self, namespace, ini_file_text): - self.namespace = namespace - self.current_namespace = self.namespace - self.errors = [] - - for parsed_line in self.ini_parser.parse(ini_file_text): - self._line_processor(**parsed_line) - - def _line_processor(self, start_section=None, - clear_section=None, - define_key=None, - error=None, **kwargs): - if start_section is not None: - self.start_section(*start_section) - elif clear_section is not None: - self.clear_section(*clear_section) - elif define_key is not None: - self.define_key(*define_key) - - for error in self.ini_parser.errors: - self.set_error(*error) - - def start_section(self, sections): - self.current_namespace = self.namespace - for section in sections: - if section not in self.current_namespace.childs: - self.current_namespace.add_namespace(Namespace(section)) - self.current_namespace = self.current_namespace[section] - - def clear_section(self, sections): - current_namespace = self.namespace - for section in sections: - if section not in current_namespace.childs: - return - current_namespace = current_namespace[section] - current_namespace.clear_childs() - - def change_value(self, key, value): - self.current_namespace[key].set_value(value) - - def define_variable(self, key, value): - self.current_namespace.add_string_variable(key, value) - - def append_value(self, key, value): - line = self.current_namespace[key].get_value().split(",") - vlist = value.split(",") - for v in vlist: - if v not in line: - line.append(v) - self.change_value(key, ",".join(line)) - - def remove_value(self, key, value): - line = self.current_namespace[key].get_value().split(",") - vlist = value.split(",") - for v in vlist: - if v in line: - line.remove(v) - self.change_value(key, ",".join(line)) - - def define_key(self, section, key, value, optype): - if optype == Define.assign: - if key not in self.current_namespace: - self.define_variable(key, value) - else: - self.change_value(key, value) - elif optype == Define.append: - if key not in self.current_namespace: - self.define_variable(key, value) - else: - self.append_value(key, value) - elif optype == Define.remove: - if key not in self.current_namespace: - self.define_variable(key, value) - else: - self.remove_value(key, value) - - def set_error(self, line, lineno, col): - self.error(lineno, "Syntax error: %s".format(line)) - - -class NamespaceIniFillerStrict(NamespaceIniFiller): - """ - Объект используемый для наполения Namespace объекта переменными - из calculate.ini файла, с возможность определять только - """ - availableSection = ["custom"] - - def fill(self, namespace, data): - self.canCreate = False - for newns in self.availableSection: - if newns not in namespace: - namespace.add_namespace(Namespace(newns)) - super().fill(namespace, data) - - def start_section(self, sections): - self.current_namespace = self.namespace - self.canCreate = sections[0] in self.availableSection - for section in sections: - if section not in self.current_namespace.childs: - if isinstance(self.current_namespace, - TableVariable) or self.canCreate: - self.current_namespace.add_namespace(Namespace(section)) - else: - self.current_namespace = None - self.current_namespace = self.current_namespace[section] - - # def clear_section(self, sections): - # current_namespace = self.namespace - # self.canCreate = sections[0] in self.availableSection - # for section in sections: - # if section not in current_namespace.childs: - # return - # current_namespace = current_namespace[section] - # current_namespace.clear_childs() - - def define_variable(self, key, value): - if not self.canCreate: - pass - var = Variable(key) - var.add_property(IniCreated(value)) - self.current_namespace.add_variable(var) - - def change_value(self, key, value): - var = self.current_namespace[key] - var.set_value(value) - - if isinstance(var, HashVariable.HashValue): - var = var.master_variable - value = var.get_value() - - prop = var.find_property(DefaultValue) - if prop: - prop.value = value - else: - var.add_property(DefaultValue(value)) - var.invalidate() - - -class VariableLoader: - '''Класс, используемый для загрузки переменных из python модуля.''' - re_upper = re.compile("(.)([A-Z])") - - def _get_varlike_attrs(self, obj): - '''Метод для получения списка аттрибутов похожих на переменные.''' - for attrname in (x for x in dir(obj) if x[:1].isupper()): - yield self.re_upper.sub(r"\1_\2", attrname).lower(), \ - getattr(obj, attrname) - - def fill(self, namespace, dirpath, package): - '''Загрузить в namespace переменные из указанных модулей.''' - for fullfn in list_directory(dirpath, fullpath=True): - dn, fn = os.path.split(fullfn) - if os.path.isdir(fullfn): - newns = namespace.add_namespace(Namespace(fn)) - self.fill(newns, fullfn, "{}.{}".format(package, fn)) - elif fn.endswith(".py"): - module = self._load_module_source(package, fn, fullfn) - for varname, cls in self._get_varlike_attrs(module): - if Variable.is_implementation(cls): - namespace.add_variable(cls(varname)) - elif HashVariable.is_implementation(cls) or \ - TableVariable.is_implementation(cls) or \ - Namespace.is_implementation(cls): - _newns = namespace.add_namespace(cls(varname, - namespace)) - for _varname, _cls in self._get_varlike_attrs(cls): - if Variable.is_implementation(_cls): - _newns.add_variable(_cls(_varname)) - - @classmethod - def default(cls): - site_dirs = [os.path.normpath(x) for x in site.getsitepackages()] - for site_dir in site_dirs: - calculate_dir = os.path.join(site_dir, "calculate/vars") - if os.path.exists(calculate_dir): - return (calculate_dir, "calculate.vars") - - def _load_module_source(self, package, name, path): - if name.startswith('calculate.vars.'): - full_name = name - else: - full_name = '.'.join([package, name]) - - if full_name in sys.modules: - return sys.modules[full_name] - - spec = importlib.util.spec_from_file_location(full_name, path) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - sys.modules[full_name] = module - return module - - -class ProfileFiller: - """ - Заполнитель значений переменных из файлов calculate.ini в профилях - """ - basename = "calculate.ini" - - def get_repository_map(self, namespace): - return { - x.name.get_value(): x.path.get_value() - for x in namespace.os.gentoo.repositories - } - - def fill(self, namespace, profile_path): - nif = NamespaceIniFillerStrict() - pw = ProfileWalker(self.basename, self.get_repository_map(namespace)) - for fn in pw.find(profile_path): - nif.fill(namespace, readFile(fn)) diff --git a/calculate/variables/parameters.py b/calculate/variables/parameters.py new file mode 100644 index 0000000..04faa42 --- /dev/null +++ b/calculate/variables/parameters.py @@ -0,0 +1,727 @@ +# vim: fileencoding=utf-8 +# +from .datavars import DependenceAPI, VariableWrapper, NamespaceNode +from .loader import Datavars +from collections import OrderedDict +from contextlib import contextmanager +from typing import Tuple, Union + + +class ParameterError(Exception): + pass + + +class ValidationError(ParameterError): + pass + + +class CyclicValidationError(ValidationError): + def __init__(self, *queue): + self.queue = queue + + def __str__(self): + return "Cyclic validation for parameters: {}".format(", ".join( + self.queue[:-1])) + + +class ParameterType: + '''Общий класс типов параметров.''' + descr = 'VALUE' + + def process_value(self, value, parameter): + return value + + +class Integer(ParameterType): + '''Класс типа целочисленных параметров.''' + descr = 'INTEGER' + + def process_value(self, value, parameter) -> int: + if isinstance(value, int): + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the int type") + + def __repr__(self): + return '' + + +class String(ParameterType): + '''Класс типа строковых параметров.''' + descr = 'STRING' + + def process_value(self, value, parameter) -> str: + if isinstance(value, str): + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the string type") + + def __repr__(self): + return '' + + +class Bool(ParameterType): + '''Класс типа строковых параметров.''' + false_set = {'n', 'false', 'False', 'no'} + true_set = {'y', 'true', 'True', 'yes'} + descr = '[y/n]' + + def process_value(self, value, parameter) -> bool: + if isinstance(value, bool): + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the bool type") + + def __repr__(self): + return '' + + +class Float(ParameterType): + '''Класс типа вещественночисловых параметров.''' + descr = 'FLOAT' + + def process_value(self, value, parameter) -> float: + if isinstance(value, float): + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the float type") + + def __repr__(self): + return '' + + +class Separator: + '''Класс объекта сепаратора, возможно, его не будет''' + def __repr__(self): + return "# # #" + + +class Choice(ParameterType): + '''Класс типа, представлющий собой любое значение из совокупности заданных. + ''' + descr = 'CHOICES' + + def __init__(self, choices=OrderedDict(), editable=False, + multichoice=False): + self.editable = editable + self.multichoice = multichoice + self.choices = choices + + def add_choice(self, choice, comment): + self.choices.update({choice: comment}) + + def process_value(self, value, parameter): + if value in self.choices: + return value + else: + raise ValidationError(f"value '{value}' is not available in" + f" parameter '{parameter._name}' available" + f" values: '{', '.join(self.choices)}'") + + def __repr__(self): + return (f'') + + +class List(ParameterType): + descr = 'LIST' + + def process_value(self, value, parameter): + if isinstance(value, list): + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the list type") + + def __repr__(self): + return ('') + + +class TableValue: + '''Класс значения таблицы.''' + def __init__(self, key, fields, fill_function=lambda x: x): + self._fields = fields + self._values = OrderedDict() + + # Родительский параметр. + self._parent = None + + # Ключ и его индекс в массиве. + self.primary_key = key + + # Устанавливаем комментарии стобцам таблицы, по умолчанию то же, что и + # название столбца. + self._fields_comments = OrderedDict() + for field in self._fields: + self._fields_comments.update({field: field}) + + # Функция для заполнения пустых полей таблицы. + self.fill = fill_function + + # Функция для кастомизации уведомления об ошибке. + def set_error(value: str, field: str, available: list) -> None: + raise ParameterError(f"{field} '{value}' is not found.") + self.set_error = set_error + + def set_comments(self, *comments): + '''Метод для установки комментариев к полям таблицы.''' + if len(comments) < len(self._fields): + raise ParameterError("Not enough values to comment table fields.") + elif len(comments) > len(self._fields): + raise ParameterError("Too much values to comment table fields.") + + self._fields_comments = OrderedDict() + for field, comment in zip(self._fields, comments): + if comment is not None: + self._fields_comments[field] = comment + + def change(self, *values): + '''Метод для добавления новых или изменения существующих строк таблицы. + ''' + if len(values) < len(self._fields): + raise ParameterError("Not enough values to fill table row.") + elif len(values) > len(self._fields): + raise ParameterError("Too much values to fill table row.") + + # Проверяем типы устанавливаемых значений. + validated_values = OrderedDict() + for value, (field, field_type) in zip(values, + self._fields.items()): + if value is None or self._parent is None: + validated_values.update({field: value}) + else: + validated_values.update({field: field_type.process_value( + value, + self._parent)}) + + current_key = validated_values[self.primary_key] + if current_key in self._values: + self._values[current_key] = validated_values + elif self._parent is None or self._parent._parameter_type.expandable: + self._values.update({current_key: validated_values}) + else: + self.set_error(current_key, + self._fields_comments[self.primary_key], + list(self._values.keys())) + + self._values = self.fill(self._values) + + def check_values_types(self): + '''Метод для проверки по типам всех значений таблицы.''' + for row in self._values.values(): + for field, field_type in self._fields.items(): + if row[field] is not None: + row[field] = field_type.process_value(row[field], + self._parent) + + def get_for_var(self): + '''Метод для преобразования данного описания таблицы в ту, которая + приемлема для инициализации переменных.''' + output = [] + for value in self._values.values(): + output.append(value) + return output + + +class Table(ParameterType): + '''Метод реализующий тип таблиц.''' + descr = 'LIST' + + def __init__(self, expandable=False): + self.expandable = expandable + + def process_value(self, value: TableValue, parameter): + if isinstance(value, TableValue): + # Если родитель не установлен -- значит параметр был только что + # инициализирован.его, и проводим полную проверку + # инициализированных значений таблицы. + if value._parent is None: + value._parent = parameter + value.check_values_types() + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the table type") + + def __repr__(self): + return (f'') + + +class File(ParameterType): + pass + + +class Password(ParameterType): + '''Класс типа строковых параметров.''' + descr = 'PASSWORD' + + def __init__(self, param=False): + # TODO разобраться с параметрами, необходимыми для параметров типа + # Password. + self.param = param + + def process_value(self, value, parameter) -> str: + if isinstance(value, str): + return value + raise ValidationError(f"Can not assign '{type(value)}' value to" + " parameter of the string type") + + def __repr__(self): + return '' + + +class Description: + '''Класс, содержащий описание параметра и способ его отображения.''' + # словарь со способами отображения параметра в графическом интерфейсе. + representations = {} + + def __init__(self, short='', full='', usage=''): + self.short = short + self.full = full + self.usage = usage + + self._gui_repr = None + + def initialize(self, parameter_name, parameter_type, shortname=None): + '''Метод для инициализации представления по указанной пользователем + информации, типу параметра и его имени.''' + if not self.short: + self.short = parameter_name + if not self.full: + self.full = self.short + if not self.usage: + if shortname: + self.usage = f"-{shortname} {parameter_type.descr}, " + self.usage = self.usage +\ + f"--{parameter_name} {parameter_type.descr}" + + self._gui_repr = self._get_gui_repr(parameter_type) + + def _get_gui_repr(self, parameter_type): + '''Метод для получения по типу параметра способа его представления.''' + # TODO реализовать его, когда удастся выделить совокупность доступных + # способов представления. + return None + + @property + def gui_repr(self): + return self._gui_repr + + +class BaseParameter: + '''Класс базовый класс всех параметров.''' + default_type = ParameterType + + def __init__(self, name, group, desctiption: Description, + shortname=None, argv=None): + self._group = group + self._name = name + self._shortname = shortname + self._argv = argv + + # Ссылка на экземпляр контейнера параметров. + self.container = None + + self._value = None + # Допустимые значения параметра, если таковые имеются. + # self.choices = OrderedDict() + # Комментарии, если таковой необходим. + self.comment = None + + # Флаг, указывающий на то, что значение параметра было изменено + # установлено пользователем с помощью метода set. + self._set_by_user = False + self._validated = False + + if hasattr(self, 'type'): + self._parameter_type = self.type + else: + self._parameter_type = self.default_type() + + # Добавляем описание и инициализируем его. + self._description = desctiption + self._description.initialize(self._name, self._parameter_type, + shortname=self._shortname) + + # Комментарий, наличие которого указывает на неактивность параметра. + self.disactivity_comment = None + + # Коллекции текущих подписок, аргументов, из которых эти подписки + # формируются и подписчиков. + self._subscriptions = {} + self._args = [] + self._subscribers = [] + + # Флаги для проверки того необходимо ли проводить поиск подписок или + # подписчиков. + self._args_is_found = False + self._sets_is_found = False + + # Метод для указания того, что в данный момент осуществляется расчет + # значения параметра. + self._calculating = False + self._validation = False + + @property + def choices(self): + if isinstance(self._parameter_type, Choice): + return self._parameter_type.choices + + @property + def disactivity(self): + return bool(self.disactivity_comment) + + def validate(self, container, datavars, value) -> None: + '''Метод для проверки корректности параметра. Переопределяется при + создании нового типа параметра.''' + pass + + def validate_value(self, value): + '''Метод для запуска валидации параметра с использованием + переопределенного метода валидации.''' + if self._validation: + raise CyclicValidationError(self._name) + with self._start_validation(): + try: + self.validate(self.container, self.container.datavars, value) + except CyclicValidationError as error: + raise CyclicValidationError(self._name, *error.queue) + + @contextmanager + def _start_validation(self): + '''Контекстный менеджер предназначеный для переключения параметра в + режим валидации.''' + try: + self._validation = True + yield self + finally: + self._validation = False + + def set(self, value) -> None: + '''Метод для установки значения параметра.''' + if isinstance(self._parameter_type, Table): + print('VALUE:', self.value) + self._value.change(*value) + else: + self._value = value + + self._set_by_user = True + # Назначаем новые значения подписанным переменным. + self._set_variables() + + @property + def value(self): + '''Метод для получения значения параметра. Выдается или дефолтное или + установленное пользователем.''' + if not self._set_by_user: + if self._value is None and not self._calculating: + self._value = self.update_value() + return self._value + + def check_value_type(self, value): + '''Метод для запуска проверки значения по типу параметра.''' + return self._parameter_type.process_value(value, self) + + def bind(self, *variables) -> None: + '''Метод для настройки взаимосвязи параметра с переменными.''' + self._args = variables + return self + + def bind_method(self, *variables): + '''Метод переопределяемый для создания нового типа параметра. Должен + возвращать вычисленное значение и активность параметра None -- активен, + comment -- дизактивирован.''' + if variables: + return variables[0].value, None + else: + return None, None + + def to_set(self, *variables): + '''Метод для добавления переменных, которым будет установлено значение + параметра.''' + self._subscribers = variables + return self + + def _set_variables(self): + '''Метод для установки значения параметра переменным-подписчикам.''' + # Только если значение установлено пользователем. + if self._set_by_user: + if not self._sets_is_found: + self._subscribers = self.find_variables(self._subscribers) + self._sets_is_found = True + for subscriber in self._subscribers: + if isinstance(self._parameter_type, Table): + subscriber.set(self.value.get_for_var()) + else: + subscriber.set(self.value) + + def _invalidate(self): + '''Метод, через который переменные сообщают о необходимости пересчитать + значение параметра с дефолтным значением.''' + value = self.update_value() + if not self._set_by_user: + self._value = value + self._validated = False + + def update_value(self): + '''Метод для получения дефолтного значения.''' + if not self._args_is_found: + self._args = self.find_variables(self._args) + self._args_is_found = True + + with self._start_calculate(): + value, subscriptions = self._calculate_binding() + + # Обновляем подписки. Сначала убираем лишние. + for subscription in self._subscriptions: + if subscription not in subscriptions: + subscription.subscribers.remove(self) + + # Теперь добавляем новые. + for subscription in subscriptions: + subscription.subscribers.add(self) + self._subscriptions = subscriptions + + return self._parameter_type.process_value(value, self) + + def _calculate_binding(self): + '''Метод для расчета значения параметра по умолчанию, используя + заданные переменные.''' + subscriptions = set() + args = tuple(VariableWrapper(arg, subscriptions) for arg in self._args) + try: + value, self.disactivity_comment = self.bind_method(*args) + return value, subscriptions + except Exception as error: + raise ParameterError('can not calculate using dependencies: {}' + ' reason: {}'.format(', '.join( + [subscription.get_fullname() + for subscription + in self._args]), + str(error))) + + @contextmanager + def _start_calculate(self): + '''Менеджер контекста устанавливающий флаг, указывающий, что данная + переменная в состоянии расчета.''' + try: + self._calculating = True + yield self + finally: + self._calculating = False + + def find_variables(self, variables): + '''Метод для поиска переменных, по их названию. Обходит список + переменных и заменяет все строки с запросами в списке на результат + поиска по запросу.''' + output = [] + for variable in variables: + if isinstance(variable, str): + variable = DependenceAPI.find_variable(variable, + self.container.datavars) + output.append(variable) + return output + + def __repr__(self): + description = self._description.short or self._description.full + return (f"") + + +class GroupWrapper: + '''Класс обертки параметров группы, который возможно будет использоваться + для организации работы графического интерфейса.''' + def __init__(self, group, container): + self.values = OrderedDict() + self.container = container + + def set_group(self): + '''Метод для установки значений параметров, осносящихся к группе.''' + self.container.set_parameters(self.values) + + +class Parameters: + '''Класс контейнера параметров.''' + def __init__(self, datavars: Datavars = None, check_order=[]): + self._datavars = datavars + self._parameters = OrderedDict() + self._validation_dict = OrderedDict() + self._order = check_order + + # Флаг указывающий на то, что в данный момент идет валидация параметров + self._validation = False + # Занятые имена параметров. + self._names = set() + # Список позиционных аргументов. + self._argvs = [] + + # Словарь функций для взаимодействия графического клиента с группами + # параметров. + self._gui_helpers = {} + + self._initialized = False + + def initialize(self, datavars: Union[Datavars, NamespaceNode]): + if not self._initialized: + self._datavars = datavars + if self._parameters: + parameters = self._parameters + self._parameters = OrderedDict() + self._add(parameters) + self._initialized = True + return self + + def add(self, *parameters: Tuple[BaseParameter]): + '''Метод для добавления некоторой совокупности параметров.''' + if not self._initialized: + self._parameters = [] + self._parameters.extend(parameters) + + for parameter in parameters: + self.add_parameter(parameter) + + def set_order(self, *parameters): + '''Метод для установки порядка проверки параметров.''' + self._order = [] + for parameter in parameters: + if isinstance(parameter, str): + parameter = self[parameter] + self._order.append(parameter) + + for parameter in self: + if parameter not in self._order: + self._order.append(parameter) + + def add_parameter(self, parameter: BaseParameter) -> None: + '''Метод для добавления параметров в контейнер.''' + if parameter._name in self._names: + raise ParameterError(f"Can not add parameter '{parameter._name}'." + " Such name has already been added. ") + elif parameter._shortname and parameter._shortname in self._names: + raise ParameterError(f"Can not add parameter '{parameter._name}'." + " Such name has already been added. ") + elif (parameter._argv is not None and + len(self._argvs) > parameter._argv and + self._argvs[parameter._argv]): + raise ParameterError(f"Can not add positional parameter " + f"'{parameter._name}'. Position" + f"'{parameter._argv}' is already taken.") + + parameter.container = self + if parameter._group in self._parameters: + self._parameters[parameter._group].append(parameter) + else: + self._parameters[parameter._group] = [parameter] + + self._names.add(parameter._name) + if parameter._shortname: + self._names.add(parameter._shortname) + if parameter._argv is not None: + # Если нужно, расширяем список позиций. + if len(self._argvs) <= parameter._argv: + while(len(self._argvs) <= parameter._argv): + self._argvs.append(None) + self._argvs[parameter._argv] = parameter + + self._order.append(parameter) + parameter.update_value() + + def set_parameters(self, parameters: OrderedDict) -> None: + '''Метод для установки значений некоторого числа параметров их + проверки.''' + # Сначала проверяем все значения по типам и составляем словарь + # валидации. + for parameter_name, value in parameters.items(): + parameter = self[parameter_name] + if not parameter.disactivity_comment: + self._validation_dict[parameter] =\ + parameter._parameter_type.process_value(value, + parameter) + parameter._validated = False + + # Теперь запускаем переопределенный метод для проверки параметров. + with self._run_validation(): + self.validate_parameters(self._validation_dict) + + def validate_parameters(self, parameters: OrderedDict): + '''Метод для запуска валидации параметров.''' + for parameter in self._order: + if parameter in self._validation_dict: + if (not parameter._validated + and not parameter.disactivity_comment): + parameter.validate_value(self._validation_dict[parameter]) + parameter._validated = True + parameter.set(self._validation_dict[parameter]) + + def validate_all(self): + for parameter in self._order: + if (not parameter._validated and + not parameter.disactivity_comment): + parameter.validate_value(self._validation_dict[parameter]) + parameter._validated = True + parameter.set(self._validation_dict[parameter]) + + @contextmanager + def _run_validation(self): + '''Контекстный менеджер предназначенный для перевода контейнера + параметров в режим валидации.''' + try: + self._validation = True + yield self + finally: + self._validation = False + self._validation_dict = OrderedDict() + + def get_group_parameters(self, group_name): + '''Метод для получения списка параметров, относящихся к указанной + группе.''' + return self._parameters[group_name] + + def get_descriptions(self): + '''Метод для получения словаря с описанием параметров.''' + output = OrderedDict() + for group, parameters in self._parameters.items(): + for parameter in parameters: + usage = ' ' + parameter._description.usage + full = parameter._description.full + + if len(usage) > 23: + usage = usage + '\n' + full = ' ' * 24 + full + elif len(usage) == 23: + usage = usage + ' ' + else: + usage = usage.ljust(23) + ' ' + + parameter_description = (f"{usage}{full}") + if group in output: + output[group].append(parameter_description) + else: + output[group] = [parameter_description] + return output + + @property + def datavars(self): + return self._datavars + + def __getitem__(self, name): + for group, parameters in self._parameters.items(): + for parameter in parameters: + if parameter._name == name or parameter._shortname == name: + if self._validation and not parameter._validated: + # В режиме валидации параметров, при попытке получить + # параметр сначала проверяем необходимость его проверки + # И если нужно проверяем его. + parameter_value =\ + self._validation_dict.get(parameter, None) or\ + parameter._value + parameter.validate_value(parameter_value) + parameter._validated = True + return parameter + else: + return parameter + raise ParameterError(f"No such parameter '{name}'.") + + def __iter__(self): + for parameters in self._parameters.values(): + for parameter in parameters: + yield parameter diff --git a/calculate/vars/main/__init__.py b/calculate/vars/main/__init__.py index d7006b2..a51fc24 100644 --- a/calculate/vars/main/__init__.py +++ b/calculate/vars/main/__init__.py @@ -1,7 +1,22 @@ -from calculate.variables.datavars import Variable, StringType +from calculate.variables.datavars import Variable, StringType, ListType ''' main: chroot -> string + cl_template_path -> string ''' -Variable('chroot', type=StringType.readonly, source='/') +Variable('cl_chroot_path', type=StringType.readonly, source='/') + +Variable('cl_template_path', type=ListType.readonly, + source=['./tests/templates/testfiles/test_runner']) + +Variable('cl_ignore_files', type=ListType.readonly, source=['*.swp']) + +Variable('cl_config_path', type=StringType.readonly, + source='/var/lib/calculate/config') + +Variable('cl_config_archive', type=StringType.readonly, + source='/var/lib/calculate/config-archive') + +Variable('cl_exec_dir_path', type=StringType.readonly, + source='/var/lib/calculate/.execute') diff --git a/calculate/vars/os/gentoo/__init__.py b/calculate/vars/os/gentoo/__init__.py index c0c0027..90b7890 100644 --- a/calculate/vars/os/gentoo/__init__.py +++ b/calculate/vars/os/gentoo/__init__.py @@ -21,7 +21,7 @@ with Namespace('profile'): def get_profile_link(make_profile): make_profile_dir = os.path.dirname(make_profile.value) try: - profile_link = read_link(make_profile) + profile_link = read_link(make_profile.value) except FilesError: return "" @@ -76,4 +76,5 @@ def get_config_object(chroot_path): # Объект текущей конфигурации Portage -Variable('config', source=Dependence('main.chroot', depend=get_config_object)) +Variable('config', source=Dependence('main.cl_chroot_path', + depend=get_config_object)) diff --git a/calculate/vars/system/__init__.py b/calculate/vars/system/__init__.py index 2a600a8..9d9f4f6 100644 --- a/calculate/vars/system/__init__.py +++ b/calculate/vars/system/__init__.py @@ -7,7 +7,7 @@ system: # Список мест, где есть calculate.ini файлы. Variable('env_order', type=ListType.readonly, - source=['grp', 'system', 'etc', 'local', 'remote']) + source=[]) # Отображение множества мест, где есть calculate.ini файлы, на пути к ним. Variable('env_path', type=HashType, source=dict()) diff --git a/pytest.ini b/pytest.ini index 8d19cee..88cf477 100644 --- a/pytest.ini +++ b/pytest.ini @@ -24,8 +24,6 @@ markers = files_utils: marker for running tests for calculate.utils.files module. package_utils: marker for running tests for calculate.utils.contents module. - old_vars: marker for running tests for datavars - vars: marker for testing alternative version of the datavars module. gentoo: marker for running tests for utils.gentoo calculateini: marker for running tests for utils.calculateini template_engine: marker for running tests for TemplateEngine. @@ -33,3 +31,9 @@ markers = template_executor: marker for running tests for TemplateAction. template_wrapper: marker for running tests for TemplateWrapper. parameters_processor: marker for running test for TemplateParameters. + + vars: marker for testing of the datavars module. + parameters: marker for testing of the parameters module. + + tasks: marker for testing of the tasks. + commands: marker for testing of the commands. diff --git a/run.py b/run.py index 6bb3919..7aff982 100644 --- a/run.py +++ b/run.py @@ -1,57 +1,111 @@ -from calculate.templates.template_engine import TemplateEngine, DIR, Variables - -from calculate.templates.template_processor import TemplateAction - -from calculate.utils.package import Version - -from calculate.utils.files import read_file_lines - -from pprint import pprint - +import os +import shutil +from calculate.utils.files import join_paths +from calculate.templates.template_engine import Variables +from calculate.utils.package import PackageAtomName, Version, Package +from calculate.templates.template_processor import DirectoryProcessor,\ + TemplateWrapper + + +CHROOT_PATH = os.path.join(os.getcwd(), + 'tests/templates/testfiles/test_dir_processor_root') +CONFIG_PATH = os.path.join(CHROOT_PATH, 'var/lib/calculate/config') +CONFIG_ARCHIVE_PATH = os.path.join(CHROOT_PATH, + 'var/lib/calculate/config-archive') +EXECUTE_ARCHIVE_PATH = os.path.join(CHROOT_PATH, + 'var/lib/calculate/.execute') + +test_package_name = PackageAtomName( + {'pkg_path': os.path.join( + CHROOT_PATH, + 'var/db/pkg/test-category/test-package-1.0'), + 'version': Version('1.0')}) + +other_package_name = PackageAtomName( + {'pkg_path': os.path.join( + CHROOT_PATH, + 'var/db/pkg/test-category/other-package-1.1'), + 'version': Version('1.1')}) + +new_package_name = PackageAtomName( + {'pkg_path': os.path.join( + CHROOT_PATH, + 'var/db/pkg/test-category/new-package-0.1.1'), + 'version': Version('1.0')}) + +# Вместо модуля переменных. +group = Variables({'bool': True, + 'list': [1, 2, 3]}) + +variables = Variables({'variable_1': 'value_1', + 'variable_2': 'value_2', + 'group': group}) + +install = Variables({'os_disk_dev': 'os_disk_dev_value', + 'version': 1.5, + 'number': 128, + 'boolean': False, + 'type': 'static', + 'path': '/usr/sbin'}) + +merge = Variables({'var_1': 674, + 'var_2': 48, + 'version': 1.0, + 'calculate_domains': 'lists.calculate-linux.org', + 'ip_value': '127.0.0.0/8'}) main = Variables({'cl_template_path': - ('tests/templates/testfiles/template_dir_1,' - 'tests/templates/testfiles/template_dir_2'), - 'cl_chroot_path': '/'}) - -values = Variables({'val_1': 11, 'val_2': 13, 'val_3': 12}) - - -datavars = Variables({'main': main, 'values': values}) - -print('\nREAD LINES EXPERIMENT\n') - -config_dictionary = {} -generator = read_file_lines('/var/lib/portage/config') -for fileline in generator: - filename, md5_sum = fileline.strip().split(' ') - config_dictionary.update({filename: md5_sum}) - -pprint(config_dictionary) - -print('\nFIRST TEMPLATE\n') -template_engine = TemplateEngine( - datavars_module=datavars, - appends_set=TemplateAction().available_appends) - -template_engine.process_template_from_string( - "{% calculate env = 'values', name = 'dir_name', append = 'join' %}", - DIR) - -template_engine.parameters.print_parameters_for_debug() - -print('\nSECOND TEMPLATE\n') -template_engine.process_template_from_string( - '''{% calculate package = 'dev-lang/python-2.7', action = 'update' -%} -{% if pkg('category/name') < '3.6.9' -%} -The package exists. -{% else -%} -The package does not exist. -{% endif -%} -{% if Version('12.3.4-r1') < '12.2.5' %} -Version is correct. -{% endif -%}''', - DIR) - -template_engine.parameters.print_parameters_for_debug() -print('\nTEMPLATE TEXT:\n{0}'.format(template_engine.template_text)) + '{0},{1}'.format(os.path.join(CHROOT_PATH, + 'templates'), + os.path.join(CHROOT_PATH, + 'var/calculate/templates')), + 'cl_chroot_path': CHROOT_PATH, + 'cl_config_path': CONFIG_PATH, + 'cl_config_archive': CONFIG_ARCHIVE_PATH, + 'cl_exec_dir_path': EXECUTE_ARCHIVE_PATH, + 'cl_ignore_files': '*.swp'}) + +test = ({'test_root': CHROOT_PATH}) + +datavars = Variables({'install': install, + 'merge': merge, + 'variables': variables, + 'main': main, + 'test': test, + 'custom': Variables()}) + + +TemplateWrapper._protected_is_set = False + +shutil.copytree(os.path.join(CHROOT_PATH, 'etc.backup'), + os.path.join(CHROOT_PATH, 'etc'), + symlinks=True) +shutil.copytree(os.path.join(CHROOT_PATH, 'var.backup'), + os.path.join(CHROOT_PATH, 'var'), + symlinks=True) + + +datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH, + 'templates_20') +directory_processor = DirectoryProcessor('install', + datavars_module=datavars) +directory_processor.process_template_directories() +test_package = Package(test_package_name, chroot_path=CHROOT_PATH) + +# Для разнообразия один из шаблонов удаляет файл, а не создает. +# Но в данном случае он ничего не сделает. +assert os.path.exists(join_paths(CHROOT_PATH, + '/etc/dir_20/file_0')) +assert '/etc/dir_20/file_0' not in test_package + +assert not os.path.islink(join_paths(CHROOT_PATH, '/etc/dir_19')) +assert os.path.isdir(join_paths(CHROOT_PATH, '/etc/dir_19')) + +assert os.path.exists(join_paths(CHROOT_PATH, + '/etc/dir_19/dir_0')) +assert os.path.exists(join_paths(CHROOT_PATH, + '/etc/dir_19/dir_0/file_0')) +assert '/etc/dir_19/dir_0/file_0' in test_package + +shutil.rmtree(os.path.join(CHROOT_PATH, 'etc')) +shutil.rmtree(os.path.join(CHROOT_PATH, 'var')) diff --git a/run_templates.py b/run_templates.py new file mode 100755 index 0000000..199637d --- /dev/null +++ b/run_templates.py @@ -0,0 +1,38 @@ +#! /usr/bin/python3 +import argparse +from calculate.templates.template_processor import DirectoryProcessor +from calculate.variables.loader import Datavars +from calculate.utils.io_module import IOModule +from calculate.utils.package import NonePackage +from calculate.utils.tools import flat_iterable + + +def main(): + parser = argparse.ArgumentParser('Run templates.') + parser.add_argument('-a', '--action', action='append', type=str, nargs='+', + help="action parameter value.") + parser.add_argument('-p', '--package', type=str, + help="atom name of a target package.") + parser.add_argument('--dbpkg', action='store_true', + help=("flag for switching template engine's mode from" + " standard mode to the mode allowing changing of" + "CONTENTS files.")) + args = parser.parse_args() + datavars = Datavars() + io_module = IOModule() + if args.package == 'None': + package = NonePackage + else: + package = args.package + + action = list(flat_iterable(args.action)) + template_processor = DirectoryProcessor(action, + datavars_module=datavars, + package=package, + output_module=io_module, + dbpkg=args.dbpkg) + template_processor.process_template_directories() + + +if __name__ == "__main__": + main() diff --git a/tests/commands/__init__.py b/tests/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py new file mode 100644 index 0000000..7f316ca --- /dev/null +++ b/tests/commands/test_commands.py @@ -0,0 +1,194 @@ +import os +import shutil +import pytest +from calculate.commands.commands import Command +from calculate.scripts.scripts import Script, Task +from calculate.variables.loader import Datavars +from calculate.variables.parameters import Parameters, BaseParameter, Integer,\ + String, ValidationError,\ + Description +from calculate.utils.io_module import IOModule + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/commands/testfiles') + + +@pytest.mark.commands +class TestCommands: + def test_to_make_testfiles(self): + shutil.copytree(os.path.join(TESTFILES_PATH, 'gentoo.backup'), + os.path.join(TESTFILES_PATH, 'gentoo'), + symlinks=True) + + def test_commands_first(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables')) + + # Скрипт выполняемый командой. + def action(output: IOModule, arg1: str) -> str: + return f'os.calculate = {arg1}' + + # Для обеспечения многопоточности вероятно придется делать так. + def test_script(): + return Script('test_script' + ).tasks(Task(id='task', + name="Task", + action=action, + args=["os.calculate"]) + ) + + # Параметры данной команды. + test_parameters = Parameters().initialize(datavars) + + class MyShinyParameter(BaseParameter): + type = Integer() + + def validate(self, var): + if var.value < 10: + raise ValidationError("The value must be greater than 10") + + def bind_method(self, var): + return var.value, None + + class AnotherParameter(BaseParameter): + type = String() + + def bind_method(self): + return 'default string', None + + def validate(self, value): + if not value.startswith('/var/lib'): + raise ValidationError("The value must starts with a" + " '/var/lib'") + + class OneMoreParameter(BaseParameter): + type = String() + + def bind_method(self): + return 'default string', None + + def validate(self, value): + available_set = {'mystery', 'horror', 'weird'} + if value not in available_set: + raise ValidationError(f"The value '{value}' is not in" + f" available. Available values:" + f" {', '.join(available_set)}") + + test_parameters.add( + MyShinyParameter('my-shiny', 'The Shiniest ones', + Description( + short='The shiny parameter', + full='The shiniest thing known to science.', + usage='-S COUNT, --my-shiny COUNT'), + shortname='S').bind('os.linux.test_5'), + AnotherParameter('my-other', 'The Others', + Description(short='Not from this world.'), + shortname='o'), + OneMoreParameter('one-more', 'The Others', + Description(full='Plan 9 from outer space.'))) + + command = Command(command_id='test', + category='Test Category', + title='Test', + script=test_script(), + parameters=test_parameters, + namespace='os', + rights=['group']) + command.initialize(datavars, IOModule()) + command.script.run() + + assert datavars.scripts.test_script.task.result ==\ + 'os.calculate = test1 test2' + + def test_commands_with_setvar(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables')) + + # Скрипт выполняемый командой. + def action(output: IOModule, arg1: str) -> str: + return f'os.calculate = {arg1}' + + # Для обеспечения многопоточности вероятно придется делать так. + def test_script(): + return Script('test_script' + ).tasks(Task(id='task', + name="Task", + action=action, + args=["os.calculate"]) + ) + + # Параметры данной команды. + test_parameters = Parameters().initialize(datavars) + + class MyShinyParameter(BaseParameter): + type = Integer() + + def validate(self, var): + if var.value < 10: + raise ValidationError("The value must be greater than 10") + + def bind_method(self, var): + return var.value, None + + class AnotherParameter(BaseParameter): + type = String() + + def bind_method(self): + return 'default string', None + + def validate(self, value): + if not value.startswith('/var/lib'): + raise ValidationError("The value must starts with a" + " '/var/lib'") + + class OneMoreParameter(BaseParameter): + type = String() + + def bind_method(self): + return 'default string', None + + def validate(self, value): + available_set = {'mystery', 'horror', 'weird'} + if value not in available_set: + raise ValidationError(f"The value '{value}' is not in" + f" available. Available values:" + f" {', '.join(available_set)}") + + test_parameters.add( + MyShinyParameter('my-shiny', 'The Shiniest ones', + Description( + short='The shiny parameter', + full='The shiniest thing known to science.', + usage='-S COUNT, --my-shiny COUNT'), + shortname='S').bind('os.linux.test_5'), + AnotherParameter('my-other', 'The Others', + Description(short='Not from this world.'), + shortname='o'), + OneMoreParameter('one-more', 'The Others', + Description(full='Plan 9 from outer space.'))) + + command = Command(command_id='test', + category='Test Category', + title='Test', + script=test_script(), + parameters=test_parameters, + namespace='os', + gui=True, + icon=['icon_1', 'icon_2'], + setvars={'os.hashvar_0': {'value1': 'new_1', + 'value2': 'new_2'}, + '.linux.test_5': 1349}, + rights=['install']) + + command.initialize(datavars, IOModule()) + command.script.run() + + assert datavars.os.hashvar_0.get_hash() == {'value1': 'new_1', + 'value2': 'new_2'} + assert datavars.os.linux.test_5 == 1349 + assert datavars.os.calculate == 'new_1 new_2' + assert datavars.scripts.test_script.task.result ==\ + 'os.calculate = new_1 new_2' + + def test_for_removing_testfiles(self): + shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo')) diff --git a/tests/commands/testfiles/calculate.ini b/tests/commands/testfiles/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/ini_vars_0.backup b/tests/commands/testfiles/gentoo.backup/ini_vars_0.backup new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/portage/calculate.ini b/tests/commands/testfiles/gentoo.backup/portage/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/portage/profiles/calculate.ini b/tests/commands/testfiles/gentoo.backup/portage/profiles/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/portage/profiles/main/calculate.ini b/tests/commands/testfiles/gentoo.backup/portage/profiles/main/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/20/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/20/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/parent b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/parent new file mode 100644 index 0000000..08bf1a2 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/desktop/parent @@ -0,0 +1,3 @@ +gentoo:main +.. +../../../desktop diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/parent b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/parent new file mode 100644 index 0000000..0c34ef3 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/20/parent @@ -0,0 +1,2 @@ +.. +../../20 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/parent b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/parent new file mode 100644 index 0000000..f3229c5 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/amd64/parent @@ -0,0 +1 @@ +.. diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/desktop/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/desktop/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/parent b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/parent new file mode 100644 index 0000000..f3229c5 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/calculate/profiles/default/parent @@ -0,0 +1 @@ +.. diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/calculate.ini new file mode 100644 index 0000000..ae500d1 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/calculate.ini @@ -0,0 +1,2 @@ +[os][linux] +arch = amd64 diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/parent b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/parent new file mode 100644 index 0000000..06dfeaf --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/amd64/parent @@ -0,0 +1,2 @@ +calculate:default/amd64/20/desktop +.. diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini new file mode 100644 index 0000000..7e78d71 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini @@ -0,0 +1,21 @@ +[os][linux] +ver = 20 +shortname = CLD +fullname = Calculate Linux Desktop +subname = KDE + +[os][hashvar] +value1 = new1 +value2 = new2 + +[os][tablevar][0] +dev = /dev/sda1 +mount = swap + +[os][tablevar][1] +dev = /dev/sda2 +mount = / + +[os][tablevar][2] +dev = /dev/sda5 +mount = /var/calculate diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/parent b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/parent new file mode 100644 index 0000000..f3229c5 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLD/parent @@ -0,0 +1 @@ +.. diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/parent b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/parent new file mode 100644 index 0000000..06dfeaf --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/amd64/parent @@ -0,0 +1,2 @@ +calculate:default/amd64/20/desktop +.. diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/parent b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/parent new file mode 100644 index 0000000..f3229c5 --- /dev/null +++ b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/CLDX/parent @@ -0,0 +1 @@ +.. diff --git a/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/calculate.ini b/tests/commands/testfiles/gentoo.backup/repos/distros/profiles/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/commands/testfiles/variables/main/__init__.py b/tests/commands/testfiles/variables/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/commands/testfiles/variables/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/commands/testfiles/variables/os/__init__.py b/tests/commands/testfiles/variables/os/__init__.py new file mode 100644 index 0000000..3b40bcb --- /dev/null +++ b/tests/commands/testfiles/variables/os/__init__.py @@ -0,0 +1,66 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType,\ + BooleanType, ListType, IntegerType + +with Namespace('linux'): + Variable('shortname', source='', type=StringType) + + Variable('ver', source='', type=StringType) + + Variable('fullname', source='', type=StringType) + + Variable('subname', source='', type=StringType) + + Variable('arch', source='', type=StringType) + + Variable('test_1', source=['choice_1', + 'choice_2', + 'choice_3', + 'choice_4'], type=ListType) + + Variable('test_2', source=False, type=BooleanType) + + Variable('test_3', source='test string', type=StringType) + + Variable('test_4', source='/dev/sda', type=StringType) + + Variable('test_5', source=8, type=IntegerType) + + Variable('test_6', source="Comment", type=StringType) + + def get_title(subname, fullname, ver): + if subname.value: + return '{} {} {}'.format(fullname.value, subname.value, ver.value) + else: + return '{} {}'.format(fullname.value, ver.value) + Variable('title', type=StringType, + source=Dependence('.subname', '.fullname', '.ver', + depend=get_title)) + +Variable('hashvar', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_0', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_1', source={'key1': 'value1', + 'key2': 'value2'}, type=HashType) + +Variable('hashvar_2', source={'id_1': 1349, + 'id_2': 1575}, type=HashType) + +Variable('calculate', type=StringType, + source=Dependence('.hashvar_0', + depend=lambda hashvar: "{} {}".format( + hashvar.value['value1'], + hashvar.value['value2']))) + +Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) + +Variable('dev_table', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) diff --git a/tests/commands/testfiles/variables/os/gentoo/__init__.py b/tests/commands/testfiles/variables/os/gentoo/__init__.py new file mode 100644 index 0000000..5b4a42e --- /dev/null +++ b/tests/commands/testfiles/variables/os/gentoo/__init__.py @@ -0,0 +1,57 @@ +import os +from calculate.variables.datavars import Variable, Namespace, Dependence,\ + StringType, TableType +''' +gentoo: + make_profile -> string + profile: + path -> string + name -> string + repositories[*]{name, path} -> table + config -> undefined +''' + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/commands/testfiles') + +# Путь до файла, указывающего на активный профиль +Variable('make_profile', type=StringType, source='/etc/portage/make.profile') + +# Параметры текущего профиля. +with Namespace('profile'): + # Абсолютный путь до профиля + Variable('path', type=StringType, + source=os.path.join(TESTFILES_PATH, + "gentoo/repos/distros/profiles/CLD/amd64")) + + def get_profile_name(path, repositories): + profile_path = path.value + if not profile_path: + return "" + + for repository in repositories.value: + repository_path = repository['path'] + repository_name = repository['name'] + remove_part = os.path.normpath(os.path.join(repository_path, + "profiles")) + if profile_path.startswith(remove_part): + return "{}:{}".format(repository_name, + profile_path[len(remove_part) + 1:]) + return profile_path + # Название профиля + Variable('name', type=StringType, + source=Dependence('.path', '..repositories', + depend=get_profile_name)) + +# Информация о репозиториях +# name: имя репозитория +# path: полный путь до репозитория +Variable('repositories', type=TableType, + source=[{'name': 'distros', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/distros")}, + {'name': 'calculate', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/calculate")}, + {'name': 'gentoo', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/portage")}]) diff --git a/tests/commands/testfiles/variables/system/__init__.py b/tests/commands/testfiles/variables/system/__init__.py new file mode 100644 index 0000000..7ccd11a --- /dev/null +++ b/tests/commands/testfiles/variables/system/__init__.py @@ -0,0 +1,18 @@ +import os +from calculate.variables.datavars import Variable, ListType, HashType +''' +system: + env_order -> list + env_path -> hash +''' + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/commands/testfiles') + + +# Список мест, где есть calculate.ini файлы. +Variable('env_order', type=ListType, source=['system']) + +# Отображение множества мест, где есть calculate.ini файлы, на пути к ним. +Variable('env_path', type=HashType, + source={'system': os.path.join(TESTFILES_PATH, 'calculate.ini')}) diff --git a/tests/scripts/__init__.py b/tests/scripts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/scripts/test_scripts.py b/tests/scripts/test_scripts.py new file mode 100644 index 0000000..553813b --- /dev/null +++ b/tests/scripts/test_scripts.py @@ -0,0 +1,1854 @@ +import pytest +from calculate.scripts.scripts import Var, Task, Static, TaskError, Done,\ + DoneAny, Success, SuccessAny, Failed,\ + FailedAny, Block, For, While, Until,\ + Handler, Script, Run, ScriptError,\ + ActionError +from calculate.variables.datavars import Namespace, Variable, IntegerType,\ + StringType, BooleanType, ListType,\ + HashType +from calculate.utils.io_module import IOModule + + +@pytest.mark.tasks +class TestTasks(): + def test_if_arguments_of_the_Var_objects_are_correct_and_they_are_called_with_datavars_namespace_and_script_namespace_arguments__this_objects_can_be_used_for_different_checks(self): + Namespace.reset() + datavars = Namespace.datavars + script_namespace = Script.make_script_variables('test_script', [], + datavars) + with Namespace('os'): + Variable('var_1', source='value', type=StringType) + + Variable('var_2', source=2, type=IntegerType) + + Variable('var_3', source=True, type=BooleanType) + + Variable('var_4', source=False, type=BooleanType) + + Variable('var_5', source=['a', 'b', 'c'], type=ListType) + + Variable('var_6', source=2, type=IntegerType) + + Variable('var_7', source='a', type=StringType) + + Variable('var_8', source='text matchtext', type=StringType) + + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + Variable('var_2', source=2, type=IntegerType) + + assert Var('os.var_3')(datavars, None, script_namespace) + + assert ~Var('os.var_4')(datavars, None, script_namespace) + + assert (Var('os.var_2') == 2)(datavars, None, script_namespace) + + assert ((Var("os.var_2") == 2) & + (Var("os.var_1") == "value"))(datavars, None, + script_namespace) + assert ~((Var("os.var_2") == 2) & + (Var("os.var_1") == "other_value"))(datavars, None, + script_namespace) + + assert ((Var("os.var_2") == 3) | + (Var("os.var_1") == "value"))(datavars, None, script_namespace) + assert ~((Var("os.var_2") == 3) | + (Var("os.var_1") == "other_value"))(datavars, None, + script_namespace) + + assert (Var("os.var_2") < 3)(datavars, None, script_namespace) + + assert (Var("os.var_2") > 1)(datavars, None, script_namespace) + + assert (Var("os.var_2") <= 3)(datavars, None, script_namespace) + assert (Var("os.var_2") <= 2)(datavars, None, script_namespace) + + assert (Var("os.var_2") >= 1)(datavars, None, script_namespace) + assert (Var("os.var_2") >= 2)(datavars, None, script_namespace) + + assert Var("os.var_5").has('a')(datavars, None, script_namespace) + assert (Var("os.var_5") << 'a')(datavars, None, script_namespace) + assert ~(Var("os.var_5") << 'd')(datavars, None, script_namespace) + + assert (Var('os.var_6') == Var('os.var_2'))(datavars, None, + script_namespace) + assert (Var('os.var_5') << Var('os.var_7'))(datavars, None, + script_namespace) + + assert (Var('.var_2') == 2)(datavars, datavars.os.linux, + script_namespace) + + assert Var('.var_8').match('match')(datavars, datavars.os, + script_namespace) + assert ~Var('.var_8').match(r'match\b')(datavars, datavars.os, + script_namespace) + + assert (Var('.var_8').regex('match', 'text ') == 'text text text' + )(datavars, datavars.os, script_namespace) + assert (Var('.var_8').regex('match\b', 'text ') != 'text text text' + )(datavars, datavars.os, script_namespace) + + assert (Var('.var_8').replace('match', 'text ') == 'text text text' + )(datavars, datavars.os, script_namespace) + + def test_if_script_object_is_created_with_one_correct_task_without_any_conditions__the_task_will_be_successfully_completed(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var', source=2, type=IntegerType) + + def action(output, arg1): + return f'os.var = {arg1}' + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=["os.var"]) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + assert datavars.scripts.test_script.task.get_hash() ==\ + {'result': 'os.var = 2', + 'success': True, + 'error_message': None} + + def test_if_script_object_is_created_with_one_correct_task_with_a_fulfilled_condition__the_task_will_be_successfully_completed(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source='var_value', type=StringType) + + Variable('var_2', source=2, type=IntegerType) + + Variable('var_3', source=True, type=BooleanType) + + def action(output, arg1, arg2): + return arg1 + ' and ' + arg2 + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=[Static('static_value'), "os.var_1"], + when=(Var('os.var_2') & Var('os.var_3'))) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + assert datavars.scripts.test_script.task.get_hash() ==\ + {'result': 'static_value and var_value', + 'success': True, + 'error_message': None} + + def test_if_script_object_is_created_with_one_correct_task_with_a_unfulfilled_condition__the_task_will_be_successfully_completed(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(output: IOModule, arg1: str): + return arg1 + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=[Static('value')], + when=~Var('os.linux.var_1')) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' not in datavars.scripts.test_script + + def test_if_script_object_is_created_with_one_failing_not_essential_task__the_script_execution_will_be_continued_and_task_result_will_contain_a_failed_status_and_error_message(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(output: IOModule, arg1: str): + raise Exception('Test error') + return arg1 + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=[Static('value')], + essential=False, + when=Var('os.linux.var_1')) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + + assert datavars.scripts.test_script.task.get_hash() ==\ + {'result': None, + 'success': False, + 'error_message': 'Test error'} + + def test_if_script_object_is_created_with_one_failing_essential_task__the_script_execution_will_be_interrupted(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(output: IOModule, arg1: str): + raise Exception('Test error') + return arg1 + + with pytest.raises(TaskError): + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=[Static('value')], + essential=True, + when=Var('os.linux.var_1')) + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_some_essential_tasks_with_conditions_and_some_conditions_uses_done_to_check_if_tasks_from_Done_or_DoneAny_arguments_were_completed__the_script_executes_tasks_which_done_condition_is_fulfilled(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + Script('test_script' + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')], + when=~Var('os.linux.var_1')), + Task(id='task_4', + name="Task 4", + action=action, + args=[Static('value_4')], + when=(Done('task_1', 'task_2') & + Var('os.linux.var_1'))), + Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')], + when=(Var('os.linux.var_1') & + Done('task_1', 'task_3'))), + Task(id='task_6', + name="Task 4", + action=action, + args=[Static('value_4')], + when=DoneAny('task_1', 'task_3')) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_3' not in datavars.scripts.test_script + assert 'task_4' in datavars.scripts.test_script + assert 'task_5' not in datavars.scripts.test_script + assert 'task_6' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_with_conditions_and_some_conditions_uses_done_to_check_if_tasks_from_Success_or_SuccessAny_arguments_were_completed__the_script_executes_tasks_witch_done_condition_is_fulfilled(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('Error lol') + return f'bad action -> {arg1}' + + Script('test_script' + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Task(id='task_3', + name="Task 3", + essential=False, + action=bad_action, + args=[Static('value_3')]), + Task(id='task_4', + name="Task 4", + action=action, + args=[Static('value_4')], + when=~Var('os.linux.var_1')), + Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')], + when=(Success('task_1', 'task_2') & + Var('os.linux.var_1'))), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')], + when=(Var('os.linux.var_1') & + Success('task_1', 'task_3'))), + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')], + when=(Success('task_1', 'task_4') & + Var('os.linux.var_1'))), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')], + when=(SuccessAny('task_1', 'task_2'))), + Task(id='task_9', + name="Task 9", + action=action, + args=[Static('value_9')], + when=(SuccessAny('task_1', 'task_3') & + Var('os.linux.var_1'))), + Task(id='task_10', + name="Task 10", + action=action, + args=[Static('value_10')], + when=(Var('os.linux.var_1') & + SuccessAny('task_3', 'task_4'))) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_4' not in datavars.scripts.test_script + assert 'task_5' in datavars.scripts.test_script + assert 'task_6' not in datavars.scripts.test_script + assert 'task_7' not in datavars.scripts.test_script + assert 'task_8' in datavars.scripts.test_script + assert 'task_9' in datavars.scripts.test_script + assert 'task_10' not in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_with_conditions_and_some_conditions_uses_done_to_check_if_tasks_from_Failed_or_FailedAny_arguments_were_completed__the_script_executes_tasks_witch_done_condition_is_fulfilled(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('Error lol') + return f'bad action -> {arg1}' + + Script('test_script' + ).tasks( + Task(id='task_0', + name="Task 0", + action=action, + args=[Static('value_0')]), + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + essential=False, + action=bad_action, + args=[Static('value_2')]), + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')], + when=~Var('os.linux.var_1')), + Task(id='task_4', + name="Task 4", + action=action, + args=[Static('value_4')], + when=(Failed('task_2', 'task_3') & + Var('os.linux.var_1'))), + Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')], + when=(Var('os.linux.var_1') & + Failed('task_1', 'task_2'))), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')], + when=Failed('task_2', 'task_3')), + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')], + when=FailedAny('task_0', 'task_1')), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')], + when=(FailedAny('task_1', 'task_2') & + Var('os.linux.var_1'))), + Task(id='task_9', + name="Task 9", + action=action, + args=[Static('value_9')], + when=(Var('os.linux.var_1') & + FailedAny('task_2', 'task_3'))) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert not datavars.scripts.test_script.task_2.success + + assert 'task_3' not in datavars.scripts.test_script + assert 'task_4' in datavars.scripts.test_script + assert 'task_5' not in datavars.scripts.test_script + assert 'task_6' in datavars.scripts.test_script + assert 'task_7' not in datavars.scripts.test_script + assert 'task_8' in datavars.scripts.test_script + assert 'task_9' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_essential_task_which_action_function_has_some_type_annotations_and_task_s_arguments_have_incorrect_types__the_script_interrupts_with_TaskError(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var', source={'key1': 'value1', + 'key2': 'value2'}, type=HashType) + + def action(arg1: str): + return arg1 + + with pytest.raises(TaskError): + Script('test_script' + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=['os.linux.var']) + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_essential_task_having_set_parameter_containing_a_string_with_path_to_an_existing_variable__the_task_s_execution_result_will_be_set_to_the_variable_from_set_parameter(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=1, type=IntegerType) + + assert datavars.os.linux.var_2 == 1 + + def action(arg1: int): + return arg1 * arg1 + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=['os.linux.var_1'], + set='os.linux.var_2') + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + assert datavars.os.linux.var_2 == 144 + + def test_if_script_object_is_created_with_essential_task_having_set_parameter_containing_a_list_of_paths_to_existing_variables__the_task_s_execution_result_will_be_set_to_the_variables_from_this_list(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=1, type=IntegerType) + + with Namespace('linux'): + Variable('var_1', source=12, type=IntegerType) + + Variable('var_2', source=1, type=IntegerType) + + assert datavars.os.linux.var_2 == 1 + + def action(arg1: int): + return arg1 * arg1 + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=['os.linux.var_1'], + set=['os.var_1', 'os.linux.var_2']) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + assert datavars.os.linux.var_2 == 144 + assert datavars.os.var_1 == 144 + + def test_if_script_object_is_created_with_essential_task_having_set_parameter_containing_a_tuple_of_paths_to_existing_variables__the_task_s_execution_result_will_be_set_to_the_variables_from_this_tuple(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=1, type=IntegerType) + + with Namespace('linux'): + Variable('var_1', source=12, type=IntegerType) + + Variable('var_2', source=1, type=IntegerType) + + assert datavars.os.linux.var_2 == 1 + + def action(arg1: int) -> int: + return arg1 * arg1 + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=['os.linux.var_1'], + set=('os.var_1', 'os.linux.var_2')) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + assert datavars.os.linux.var_2 == 144 + assert datavars.os.var_1 == 144 + + def test_if_script_object_is_created_with_essential_task_having_set_parameter_containing_a_dict_with_field_names_and_paths_to_existing_variables_and_action_function_returns_dictionary_with_some_fields_from_the_first_dict__the_values_from_dict_returned_by_action_function_will_be_set_to_the_variables_from_the_same_field(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source='value_1', type=StringType) + Variable('var_2', source='value_2', type=StringType) + + with Namespace('linux'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=24, type=IntegerType) + + def action(arg1: int, arg2: int): + return {'field_1': f'value_{arg1}', + 'field_2': f'value_{arg2}'} + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=['os.linux.var_1', 'os.linux.var_2'], + set={'field_1': 'os.var_1', 'field_2': 'os.var_2'}) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task' in datavars.scripts.test_script + assert datavars.os.var_1 == 'value_12' + assert datavars.os.var_2 == 'value_24' + + def test_if_script_object_is_created_with_essential_task_having_action_function_with_a_return_type_annotation_and_action_function_returns_value_of_different_type__the_task_fails(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source='value', type=StringType) + + def action(arg1: str) -> str: + return len(arg1) + + with pytest.raises(TaskError): + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=['os.linux.var_1']) + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_essential_task_having_action_function_with_a_return_type_annotation_and_set_parameter_containing_variable_of_the_different_type__the_task_fails_to_set_returned_value_to_this_variable(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source='value', type=StringType) + Variable('var_2', source=12, type=IntegerType) + + def action(arg1: str) -> str: + return arg1 + + with pytest.raises(TaskError): + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=['os.linux.var_1'], + set='os.linux.var_2') + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_plain_block_of_correct_tasks__the_script_will_be_executed_successfully(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')])) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert datavars.scripts.test_script.task_1.success + assert 'task_2' in datavars.scripts.test_script + assert datavars.scripts.test_script.task_2.success + + def test_if_script_object_is_created_with_block_with_fulfilled_condition__the_script_will_execute_block_s_tasks_successfully(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + when=Var('os.linux.var_1')) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_block_with_unfulfilled_condition__the_script_will_ignore_block_s_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + when=~Var('os.linux.var_1')) + ).initialize(IOModule(), datavars, None)() + + assert 'task_3' not in datavars.scripts.test_script + assert 'task_4' not in datavars.scripts.test_script + + def test_if_script_object_is_created_with_block_of_essential_tasks_having_rescue_tasks_and_one_of_them_fails__the_script_interrupts_execution_of_the_block_s_tasks__executes_tasks_from_the_rescue_and_interrupts_whole_execution(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('Error lol') + return f'bad action -> {arg1}' + + with pytest.raises(TaskError): + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=bad_action, + args=[Static('value_2')]) + ).rescue( + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('rescue_value')])) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' not in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_block_of_essential_tasks_having_loop_parameter_and_there_is_For_loop_with_static_list_in_it__the_block_tasks_will_be_iterated_using_values_from_this_list(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + output = [] + expected = ['action_1 -> value_1', + 'action_2 -> value_1', + 'action_1 -> value_2', + 'action_2 -> value_2', + 'action_1 -> value_3', + 'action_2 -> value_3'] + + def action_1(arg1: str): + output.append(f'action_1 -> {arg1}') + return f'action_1 -> {arg1}' + + def action_2(arg1: str): + output.append(f'action_2 -> {arg1}') + return f'action_2 -> {arg1}' + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action_1, + args=['scripts.test_script.value']), + Task(id='task_2', + name="Task 2", + action=action_2, + args=['scripts.test_script.value']), + loop=For('value', ['value_1', 'value_2', 'value_3'])) + ).initialize(IOModule(), datavars, None)() + + for l_value, r_value in zip(output, expected): + assert l_value == r_value + + def test_if_script_object_is_created_with_block_of_essential_tasks_having_loop_parameter_and_there_is_For_loop_with_a_list_variable_in_it__the_block_tasks_will_be_iterated_using_values_from_this_variable(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=['value_1', 'value_2', 'value_3'], + type=ListType) + + output = [] + expected = ['action_1 -> value_1', + 'action_2 -> value_1', + 'action_1 -> value_2', + 'action_2 -> value_2', + 'action_1 -> value_3', + 'action_2 -> value_3'] + + def action_1(arg1: str): + output.append(f'action_1 -> {arg1}') + return f'action_1 -> {arg1}' + + def action_2(arg1: str): + output.append(f'action_2 -> {arg1}') + return f'action_2 -> {arg1}' + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action_1, + args=['scripts.test_script.value']), + Task(id='task_2', + name="Task 2", + action=action_2, + args=['scripts.test_script.value']), + loop=For('value', 'os.linux.var_1')) + ).initialize(IOModule(), datavars, None)() + + for l_value, r_value in zip(output, expected): + assert l_value == r_value + + def test_if_script_object_is_created_with_block_of_one_essential_task_having_loop_parameter_and_there_is_While_loop_with_a_condition_in_it__the_block_tasks_will_be_iterated_while_condition_is_true(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('counter', source=0, type=IntegerType) + Variable('ceiling', source=5, type=IntegerType) + Variable('output', source=[], type=ListType) + + expected = ['counter = 0', + 'counter = 1', + 'counter = 2', + 'counter = 3', + 'counter = 4'] + + def action(counter: int, out: list): + out.append(f'counter = {counter}') + return {'counter': counter + 1, 'output': out} + + Script('test_script' + ).tasks( + Block(Task(id='task', + name="Task", + action=action, + args=['os.counter', 'os.output'], + set={'counter': 'os.counter', + 'output': 'os.output'}), + loop=While(Var('os.counter') < Var('os.ceiling'))) + ).initialize(IOModule(), datavars, None)() + + assert datavars.os.output == expected + + def test_if_script_object_is_created_with_block_of_two_essential_tasks_having_loop_parameter_and_there_is_While_loop_with_a_condition_in_it__the_block_tasks_will_be_iterated_while_condition_is_true(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('counter', source=0, type=IntegerType) + Variable('ceiling', source=5, type=IntegerType) + Variable('flag', source=False, type=BooleanType) + Variable('output', source=[], type=ListType) + + expected = ['counter = 0', + 'counter = 1', + 'counter = 2', + 'counter = 3', + 'counter = 4'] + + def action_1(counter: int, out: list): + out.append(f'counter = {counter}') + return {'counter': counter + 1, + 'output': out} + + def action_2(counter: int, ceiling: int): + return not (counter < ceiling) + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action_1, + args=['os.counter', 'os.output'], + set={'counter': 'os.counter', + 'output': 'os.output'}), + Task(id='task_2', + name="Task 2", + action=action_2, + args=['os.counter', 'os.ceiling'], + set='os.flag'), + loop=While(~Var('os.flag'))) + ).initialize(IOModule(), datavars, None)() + + assert datavars.os.output == expected + + def test_if_script_object_is_created_with_block_of_two_essential_tasks_having_loop_parameter_and_there_is_Until_loop_with_a_unfulfilled_condition_in_it__the_block_tasks_will_be_iterated_one_time(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('counter', source=0, type=IntegerType) + Variable('flag', source=False, type=BooleanType) + Variable('output', source=[], type=ListType) + + def action(counter: int, out: list): + out.append(f'counter = {counter}') + return {'counter': counter + 1, + 'output': out} + + Script('test_script' + ).tasks( + Block(Task(id='task', + name="Task", + action=action, + args=['os.counter', 'os.output'], + set={'counter': 'os.counter', + 'output': 'os.output'}), + loop=Until(Var('os.flag'))) + ).initialize(IOModule(), datavars, None)() + + assert datavars.os.output == ['counter = 0'] + + def test_if_script_object_is_created_with_block_of_two_essential_tasks_having_loop_parameter_and_there_is_Until_loop_with_a_condition_in_it__the_block_tasks_will_be_iterated_several_times(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('counter', source=0, type=IntegerType) + Variable('ceiling', source=5, type=IntegerType) + Variable('flag', source=False, type=BooleanType) + Variable('output', source=[], type=ListType) + + expected = ['counter = 0', + 'counter = 1', + 'counter = 2', + 'counter = 3', + 'counter = 4'] + + def action_1(counter: int, out: list): + out.append(f'counter = {counter}') + return {'counter': counter + 1, + 'output': out} + + def action_2(counter: int, ceiling: int): + return not (counter < ceiling) + + Script('test_script' + ).tasks( + Block(Task(id='task_1', + name="Task 1", + action=action_1, + args=['os.counter', 'os.output'], + set={'counter': 'os.counter', + 'output': 'os.output'}), + Task(id='task_2', + name="Task 2", + action=action_2, + args=['os.counter', 'os.ceiling'], + set='os.flag'), + loop=Until(~Var('os.flag'))) + ).initialize(IOModule(), datavars, None)() + + assert datavars.os.output == expected + + def test_if_script_object_is_created_with_a_handler_and_some_task_has_notify_parameter_with_this_handler_s_id_in_it__the_script_executes_tasks_from_this_handler(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + with Namespace('linux'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + Script('test_script' + ).tasks( + Task(id='task', + name="Task", + action=action, + args=[Static('value_1')], + notify='handler'), + Handler('handler', + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]) + ) + ).initialize(IOModule(), datavars, None)() + + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_tasks_block_with_rescue_and_handler_which_id_is_in_the_notify_parameter_of_the_correct_tasks__the_script_will_execute_tasks_from_script_root__block_tasks_and_tasks_from_handler_but_tasks_from_rescue_will_be_not_executed(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + Script('test_script', + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Block(Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + Task(id='task_4', + name="Task 4", + action=action, + args=[Static('value_4')], + notify='handler_1'), + when=Var('os.var_1') + ).rescue(Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')])), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')]), + Handler('handler_1', + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')]), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')]) + ) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + assert 'task_4' in datavars.scripts.test_script + assert 'task_5' not in datavars.scripts.test_script + assert 'task_6' in datavars.scripts.test_script + assert 'task_7' in datavars.scripts.test_script + assert 'task_8' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_block_with_failing_task_and_rescue_and_handler_which_id_is_in_the_notify_parameter_of_the_failing_task__the_script_will_execute_tasks_before_block_and_first_task_of_block_and_then_will_fail_and_execute_rescue_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('very bad error') + return f'bad_{arg1}' + + with pytest.raises(TaskError): + Script('test_script', + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Block(Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + Task(id='task_4', + name="Task 4", + action=bad_action, + args=[Static('value_4')], + notify='handler_1'), + when=Var('os.var_1') + ).rescue(Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')])), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')]), + Handler('handler_1', + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')]), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')]) + ) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + assert 'task_4' not in datavars.scripts.test_script + assert 'task_5' in datavars.scripts.test_script + assert 'task_6' not in datavars.scripts.test_script + assert 'task_7' not in datavars.scripts.test_script + assert 'task_8' not in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_block_with_failing_task_and_rescue_and_handler_which_id_is_in_the_notify_parameter_of_the_block_task_before_failing_task__the_script_will_execute_tasks_before_block_and_first_task_of_block_and_then_will_fail_execute_rescue_tasks_and_tasks_from_handler(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('very bad error') + return f'bad_{arg1}' + + with pytest.raises(TaskError): + Script('test_script', + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Block(Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')], + notify='handler_1'), + Task(id='task_4', + name="Task 4", + action=bad_action, + args=[Static('value_4')]), + when=Var('os.var_1') + ).rescue(Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')])), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')]), + Handler('handler_1', + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')]), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')]) + ) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + assert 'task_4' not in datavars.scripts.test_script + assert 'task_5' in datavars.scripts.test_script + assert 'task_6' not in datavars.scripts.test_script + assert 'task_7' in datavars.scripts.test_script + assert 'task_8' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_block_with_failing_unessential_task_and_rescue_and_handler_which_id_is_in_the_notify_parameter_of_the_failing_unessential_task__the_script_will_execute_tasks_before_block_and_all_tasks_of_block_except_failing_task(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('not so bad error') + return f'bad_{arg1}' + + Script('test_script', + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Block(Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + Task(id='task_4', + name="Task 4", + essential=False, + action=bad_action, + args=[Static('value_4')], + notify='handler_1'), + when=Var('os.var_1') + ).rescue(Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')])), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')]), + Handler('handler_1', + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')]), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')]) + ) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + assert 'task_4' in datavars.scripts.test_script + assert not datavars.scripts.test_script.task_4.success + assert datavars.scripts.test_script.task_4.error_message ==\ + 'not so bad error' + + assert 'task_5' not in datavars.scripts.test_script + assert 'task_6' in datavars.scripts.test_script + assert 'task_7' not in datavars.scripts.test_script + assert 'task_8' not in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_one_task_with_unfulfilled_condition_block_with_tasks_and_rescue_and_handler_which_id_is_in_the_notify_parameter_of_the_task_with_unfulfilled_condition__the_script_will_execute_tasks_from_script_root_except_task_with_condition_tasks_and_all_tasks_of_block(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('not so bad error') + return f'bad_{arg1}' + + Script('test_script', + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')], + notify='handler_1', + when=~Var('os.var_1')), + Block(Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + Task(id='task_4', + name="Task 4", + action=action, + args=[Static('value_4')]), + when=Var('os.var_1') + ).rescue(Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')])), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')]), + Handler('handler_1', + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')]), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')]) + ) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' not in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + assert 'task_4' in datavars.scripts.test_script + assert 'task_5' not in datavars.scripts.test_script + assert 'task_6' in datavars.scripts.test_script + assert 'task_7' not in datavars.scripts.test_script + assert 'task_8' not in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_essential_tasks_one_block_of_tasks_having_fulfilled_condition_with_Done_and_rescue_block_and_handler_which_id_is_in_the_notify_parameter_of_the_successful_task__the_script_will_execute_all_tasks_except_task_from_rescue_block(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1: str): + return f'action -> {arg1}' + + def bad_action(arg1: str): + raise Exception('not so bad error') + return f'bad_{arg1}' + + Script('test_script', + args=['arg1', 'arg_2'], + ).tasks( + Task(id='task_1', + name="Task 1", + action=action, + args=[Static('value_1')]), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')], + notify='handler_1', + when=Var('scripts.test_script.arg1')), + Block(Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + Task(id='task_4', + name="Task 4", + action=action, + args=[Static('value_4')]), + when=Done('task_1', 'task_2') + ).rescue(Task(id='task_5', + name="Task 5", + action=action, + args=[Static('value_5')])), + Task(id='task_6', + name="Task 6", + action=action, + args=[Static('value_6')]), + Handler('handler_1', + Task(id='task_7', + name="Task 7", + action=action, + args=[Static('value_7')]), + Task(id='task_8', + name="Task 8", + action=action, + args=[Static('value_8')]) + ) + ).initialize(IOModule(), datavars, None)(True, False) + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_2' in datavars.scripts.test_script + assert 'task_3' in datavars.scripts.test_script + assert 'task_4' in datavars.scripts.test_script + assert 'task_5' not in datavars.scripts.test_script + assert 'task_6' in datavars.scripts.test_script + assert 'task_7' in datavars.scripts.test_script + assert 'task_8' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_any_number_of_tasks_and_Run_that_is_special_object_for_running_other_scripts_and_Run_have_no_arguments_for_running_script__the_script_executes_script_from_the_Run_object_and_its_own_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + ).tasks(Task(id='task_1', + name="Task", + action=action, + args=[Static('value_1')])) + + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', when=Var('os.var_1')), + Task(id='task_2', + name="Task", + action=action, + args=[Static('value_2')]), + ).initialize(IOModule(), datavars, None)() + + assert 'test_script_1' in datavars.scripts + assert 'test_script_2' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script_1 + assert 'task_2' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_1.task_1.result ==\ + 'value = value_1' + assert datavars.scripts.test_script_2.task_2.result ==\ + 'value = value_2' + + def test_if_script_object_is_created_with_any_number_of_tasks_and_Run_object_is_used_to_run_other_script_with_static_argument__the_script_executes_script_from_the_Run_object_with_specified_static_arguments_and_its_own_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg1'] + ).tasks(Task(id='task_1', + name="Task", + action=action, + args=["scripts.test_script_1.arg1"])) + + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=[Static('arg_value')], + when=Var('os.var_2')), + Task(id='task_2', + name="Task", + action=action, + args=[Static('value_2')]), + ).initialize(IOModule(), datavars, None)() + + assert 'test_script_1' in datavars.scripts + assert 'test_script_2' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script_1 + assert 'task_2' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_1.task_1.result ==\ + 'value = arg_value' + assert datavars.scripts.test_script_2.task_2.result ==\ + 'value = value_2' + + def test_if_script_object_is_created_with_any_number_of_tasks_and_Run_object_is_used_to_run_other_script_with_variable_as_its_only_argument__the_script_executes_script_from_the_Run_object_with_specified_variable_arguments_and_its_own_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg1'] + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=["scripts.test_script_1.arg1"])) + + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=['os.var_1'], + when=Var('os.var_2')), + Task(id='task_2', + name="Task", + action=action, + args=[Static('value_2')]), + ).initialize(IOModule(), datavars, None)() + + assert 'test_script_1' in datavars.scripts + assert 'test_script_2' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script_1 + assert 'task_2' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_1.task_1.result ==\ + 'value = 12' + assert datavars.scripts.test_script_2.task_2.result ==\ + 'value = value_2' + + def test_if_script_object_is_created_with_any_number_of_tasks_and_Run_object_is_used_to_run_other_script_with_two_variables_as_its_arguments__the_script_executes_script_from_the_Run_object_with_specified_variables_arguments_and_its_own_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source='value', type=StringType) + Variable('var_3', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg1', 'arg2'] + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=["scripts.test_script_1.arg1"]), + Task(id='task_2', + name="Task 2", + action=action, + args=["scripts.test_script_1.arg2"])) + + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=['os.var_1', 'os.var_2'], + when=Var('os.var_3')), + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + ).initialize(IOModule(), datavars, None)() + + assert 'test_script_1' in datavars.scripts + assert 'test_script_2' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script_1 + assert 'task_2' in datavars.scripts.test_script_1 + assert 'task_3' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_1.task_1.result ==\ + 'value = 12' + assert datavars.scripts.test_script_1.task_2.result ==\ + 'value = value' + assert datavars.scripts.test_script_2.task_3.result ==\ + 'value = value_3' + + def test_if_script_object_is_created_with_any_number_of_tasks_and_Run_object_is_used_to_run_other_script_with_insufficient_number_of_variables_as_its_arguments__the_outer_script_interrupts_its_execution_with_error(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source='value', type=StringType) + Variable('var_3', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg1', 'arg2'] + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=["scripts.test_script_1.arg1"]), + Task(id='task_2', + name="Task 2", + action=action, + args=["scripts.test_script_1.arg2"])) + + with pytest.raises(ScriptError): + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=['os.var_1'], + when=Var('os.var_3')), + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_any_number_of_tasks_and_Run_object_is_used_to_run_other_script_with_excess_number_of_variables_as_its_arguments__the_outer_script_interrupts_its_execution_with_error(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source='value', type=StringType) + Variable('var_3', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg1', 'arg2'] + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=["scripts.test_script_1.arg1"]), + Task(id='task_2', + name="Task 2", + action=action, + args=["scripts.test_script_1.arg2"])) + + with pytest.raises(ScriptError): + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=['os.var_1', 'os.var2', Static('value')], + when=Var('os.var_3')), + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]), + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_any_number_of_tasks_and_two_Run_objects_that_are_used_to_run_the_same_scripts_with_different_arguments__the_outer_script_runs_script_from_Run_objects_two_times_with_different_arguments(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg1'] + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=["scripts.test_script_1.arg1"])) + + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=[Static(12)], + when=Var('os.var_2')), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]), + Run(script_1, namespace='os', + args=[Static(13)]) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script_1' in datavars.scripts + assert 'test_script_2' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script_1 + assert 'task_2' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_1.arg1 == 13 + assert datavars.scripts.test_script_1.task_1.result ==\ + 'value = 13' + assert datavars.scripts.test_script_2.task_2.result ==\ + 'value = value_2' + + def test_if_script_object_is_created_with_any_number_of_tasks_and_essential_Run_object_is_used_to_run_other_failing_script__the_outer_script_interrupts_its_execution_with_error(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + def bad_action(arg): + raise Exception('error lol') + return f'value = {arg}' + + script_1 = Script('test_script_1', + args=['arg1'] + ).tasks(Task(id='task_1', + name="Task 1", + action=bad_action, + args=["scripts.test_script_1.arg1"])) + + with pytest.raises(ScriptError): + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=[Static(12)], + when=Var('os.var_2'), + essential=True), + Task(id='task_2', + name="Task 2", + action=action, + args=[Static('value_2')]) + ).initialize(IOModule(), datavars, None)() + + def test_if_script_object_is_created_with_any_number_of_tasks_and_not_essential_Run_object_is_used_to_run_other_failing_script__the_outer_script_skips_failing_script_and_continues_to_execute_its_own_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + def bad_action(arg): + raise Exception('error lol') + return f'value = {arg}' + + script_1 = Script('test_script_1', + args=['arg1'] + ).tasks(Task(id='task_1', + name="Task 1", + essential=False, + action=bad_action, + args=["scripts.test_script_1.arg1"]), + Task(id='task_2', + name="Task 2", + action=bad_action, + args=["scripts.test_script_1.arg1"])) + + Script('test_script_2', + ).tasks(Run(script_1, namespace='os', + args=[Static(12)], + when=Var('os.var_2'), + essential=False), + Task(id='task_3', + name="Task 3", + action=action, + args=[Static('value_3')]) + ).initialize(IOModule(), datavars, None)() + + assert 'task_1' in datavars.scripts.test_script_1 + assert not datavars.scripts.test_script_1.task_1.success + assert datavars.scripts.test_script_1.task_1.error_message ==\ + "error lol" + assert 'task_3' in datavars.scripts.test_script_2 + + def test_if_script_object_is_created_with_essential_task_which_action_raises_ActionError_exception_or_an_exception_inheriting_it__the_outer_script_skips_failing_task_as_if_it_is_not_essential_task(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + class ValueError(ActionError): + pass + + def action(arg1): + if arg1 < 15: + raise ValueError('action based error') + return f'value = {arg1}' + + Script('test_script', + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=['os.var_1']) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + assert 'task_1' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_not_essential_task_which_action_raises_ActionError_exception_or_an_exception_inheriting_it__the_outer_script_skips_failing_task(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=12, type=IntegerType) + Variable('var_2', source=True, type=BooleanType) + + class ValueError(ActionError): + pass + + def action(arg1): + if arg1 < 15: + raise ValueError('action based error') + return f'value = {arg1}' + + Script('test_script', + ).tasks(Task(id='task_1', + name="Task 1", + essential=False, + action=action, + args=['os.var_1']) + ).initialize(IOModule(), datavars, None)() + + assert 'test_script' in datavars.scripts + assert 'task_1' in datavars.scripts.test_script + + def test_if_script_object_is_created_with_some_tasks_and_Run_objects_are_used_to_run_scripts_and_different_namespaces_is_set_in_it__the_outer_script_runs_scripts_from_Run_objects_for_different_namespaces_and_its_own_tasks(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('os'): + Variable('var_1', source=1349, type=IntegerType) + Variable('var_2', source='value_2', type=StringType) + + with Namespace('linux'): + Variable('var_1', source=1507, type=IntegerType) + Variable('var_3', source='value_3', type=StringType) + Variable('var_4', source=True, type=BooleanType) + + def action(arg1): + return f'value = {arg1}' + + script_1 = Script('test_script_1', + args=['arg'] + ).tasks(Task(id='task_1', + name="Task 1", + action=action, + args=['.var_1'], + when=Var('scripts.test_script_1.arg')), + Task(id='task_2', + name="Task 2", + action=action, + args=['.var_1'], + when=~Var('scripts.test_script_1.arg'))) + + Script('test_script_2', + ).tasks(Task(id='task_1', + name="Task 1", + essential=False, + action=action, + args=['.var_1']), + Block(Task(id='task_2', + name="Task 2", + essential=False, + action=action, + args=['.var_2']), + Task(id='task_3', + name="Task 3", + essential=False, + action=action, + args=['.linux.var_3']), + when=Var('.linux.var_4')), + Run(script_1, namespace='os.linux', args=[True], + when=Var('.linux.var_4')), + Run(script_1, namespace='os', args=[False], + when=Var('.linux.var_4')) + ).initialize(IOModule(), datavars, datavars.os)() + + assert 'test_script_1' in datavars.scripts + assert 'test_script_2' in datavars.scripts + + assert 'task_1' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_2.task_1.result == 'value = 1349' + + assert 'task_2' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_2.task_2.result ==\ + 'value = value_2' + + assert 'task_3' in datavars.scripts.test_script_2 + assert datavars.scripts.test_script_2.task_3.result ==\ + 'value = value_3' + + assert 'task_1' in datavars.scripts.test_script_1 + assert datavars.scripts.test_script_1.task_1.result == 'value = 1507' + + assert 'task_2' in datavars.scripts.test_script_1 + assert datavars.scripts.test_script_1.task_2.result == 'value = 1349' diff --git a/tests/templates/test_directory_processor.py b/tests/templates/test_directory_processor.py index 26c80f3..074ba46 100644 --- a/tests/templates/test_directory_processor.py +++ b/tests/templates/test_directory_processor.py @@ -860,6 +860,54 @@ class TestDirectoryProcessor: file_text = file_16.read() assert file_text == 'There is a value = 128' + def test_multiple_actions(self): + datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH, + 'templates_32') + directory_processor = DirectoryProcessor(['install', 'update'], + datavars_module=datavars) + directory_processor.process_template_directories() + test_package = Package(test_package_name, chroot_path=CHROOT_PATH) + + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_32')) + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_32/file_0')) + assert '/etc/dir_32/file_0' in test_package + + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_33')) + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_33/file_0')) + assert '/etc/dir_33/file_0' in test_package + + def test_None_package(self): + datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH, + 'templates_33') + directory_processor = DirectoryProcessor(['install', 'update'], + datavars_module=datavars) + directory_processor.process_template_directories() + test_package = Package(test_package_name, chroot_path=CHROOT_PATH) + + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_35')) + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_35/file_0')) + assert '/etc/dir_35/file_0' in test_package + + def test_multiple_package_value(self): + datavars.main['cl_template_path'] = os.path.join(CHROOT_PATH, + 'templates_34') + directory_processor = DirectoryProcessor(['install', 'update'], + datavars_module=datavars) + directory_processor.process_template_directories() + test_package = Package(test_package_name, chroot_path=CHROOT_PATH) + + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_36')) + assert os.path.exists(join_paths(CHROOT_PATH, + 'etc/dir_36/file_0')) + assert '/etc/dir_36/file_0' in test_package + def test_view_tree(self): list_path = join_paths(CHROOT_PATH, '/etc') show_tree(list_path) diff --git a/tests/templates/test_template_engine.py b/tests/templates/test_template_engine.py index 2794bc8..f2d5552 100644 --- a/tests/templates/test_template_engine.py +++ b/tests/templates/test_template_engine.py @@ -148,7 +148,7 @@ class TestTemplateEngine(): def test_if_an_input_template_contains_pkg_function_with_package_that_does_not_exist_in_its_argument__it_works_correctly_and_pkg_function_returns_empty_version_value(self): input_template = '''{% calculate name = 'filename', force -%} - {% if pkg('category/name') == Version() -%} + {% if pkg() == Version() -%} pkg() works correctly. {%- else -%} pkg() does not work correctly. @@ -202,3 +202,50 @@ class TestTemplateEngine(): template_engine.process_template_from_string(input_template, FILE) except ConditionFailed: pytest.fail('Unexpected ConditionFailed exception.') + + def test_two_template_engines(self): + input_template_1 = '''{% calculate name = 'filename', force -%} +parameter_1 = {{ vars_1.var_1 }}''' + input_template_2 = '''{% calculate name = 'filename', force -%} +parameter_2 = {{ vars_1.var_1 }}''' + + output_text_1 = '''parameter_1 = first''' + output_text_2 = '''parameter_2 = second''' + + datavars_module_1 = Variables({'vars_1': + Variables({'var_1': 'first'})}) + datavars_module_2 = Variables({'vars_1': + Variables({'var_1': 'second'})}) + + template_engine_1 = TemplateEngine(appends_set=APPENDS_SET, + datavars_module=datavars_module_1) + template_engine_1.process_template_from_string(input_template_1, DIR) + + template_engine_2 = TemplateEngine(appends_set=APPENDS_SET, + datavars_module=datavars_module_2) + template_engine_2.process_template_from_string(input_template_2, DIR) + + text_1 = template_engine_1.template_text + text_2 = template_engine_2.template_text + + assert text_1 == output_text_1 + assert text_2 == output_text_2 + + def test_pkg_with_vars(self): + datavars_module = Variables({'vars_1': + Variables({'var_1': + 'test-category/test-package', + 'var_2': 1.2})}) + input_template = '''{% calculate name = 'filename', force -%} + {% if pkg(vars_1.var_1) < 2.7 -%} + pkg() works correctly. + {%- else -%} + pkg() does not work correctly. + {%- endif -%}''' + output_text = 'pkg() works correctly.' + template_engine = TemplateEngine(appends_set=APPENDS_SET, + datavars_module=datavars_module) + template_engine.process_template_from_string(input_template, FILE) + + text = template_engine.template_text + assert text == output_text diff --git a/tests/templates/test_template_executor.py b/tests/templates/test_template_executor.py index 1085d40..fccc659 100644 --- a/tests/templates/test_template_executor.py +++ b/tests/templates/test_template_executor.py @@ -3,16 +3,12 @@ import grp import stat import shutil import pytest -import getpass import hashlib -from pprint import pprint from collections import OrderedDict from calculate.templates.template_processor import TemplateExecutor, DIR,\ FILE, CalculateConfigFile,\ TemplateExecutorError,\ - TemplateWrapper,\ - TemplateCollisionError,\ - TemplateTypeConflict + TemplateWrapper from calculate.templates.template_engine import ParametersContainer from calculate.utils.package import PackageAtomName, Version, Package from calculate.utils.files import join_paths @@ -2739,9 +2735,8 @@ print(os.getcwd())''' shutil.rmtree(EXECUTE_ARCHIVE_PATH) template_executor.execute_files = OrderedDict() - target_path = join_paths( - CHROOT_PATH, - '/etc/exec_parameter_testfiles/dir_1/file_0') + target_path = join_paths(CHROOT_PATH, + '/etc/exec_parameter_testfiles/dir_1/file_0') parameters_object = ParametersContainer({'package': test_package_name, 'exec': '/usr/bin/python'}) with pytest.raises(TemplateExecutorError): diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_32/install/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_32/install/.calculate_directory new file mode 100644 index 0000000..969b628 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_32/install/.calculate_directory @@ -0,0 +1,3 @@ +{% calculate append = 'skip', action = 'install', +package = 'test-category/test-package' %} +{% calculate path = '/etc' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/.calculate_directory new file mode 100644 index 0000000..6390c76 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/file_0 b/tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/file_0 new file mode 100644 index 0000000..ba8de1e --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_32/install/dir_32/file_0 @@ -0,0 +1,5 @@ +{% calculate append = 'join', format = 'bind' -%} +options { + parameter-1 {{ variables.variable_1 }}; + parameter-2 {{ variables.variable_2 }}; +}; diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_32/update/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_32/update/.calculate_directory new file mode 100644 index 0000000..2c74513 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_32/update/.calculate_directory @@ -0,0 +1,3 @@ +{% calculate append = 'skip', action = 'update', +package = 'test-category/test-package' %} +{% calculate path = '/etc' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/.calculate_directory new file mode 100644 index 0000000..6390c76 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/file_0 b/tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/file_0 new file mode 100644 index 0000000..ba8de1e --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_32/update/dir_33/file_0 @@ -0,0 +1,5 @@ +{% calculate append = 'join', format = 'bind' -%} +options { + parameter-1 {{ variables.variable_1 }}; + parameter-2 {{ variables.variable_2 }}; +}; diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_33/install/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_33/install/.calculate_directory new file mode 100644 index 0000000..da1e1b6 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_33/install/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', action = 'install' %} +{% calculate path = '/etc' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/.calculate_directory new file mode 100644 index 0000000..8e3df7f --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'skip' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/file_0 b/tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/file_0 new file mode 100644 index 0000000..d253f96 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_33/install/dir_34/file_0 @@ -0,0 +1 @@ +{% calculate merge = 'test-category/test-package' -%} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_33/update/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_33/update/.calculate_directory new file mode 100644 index 0000000..2c74513 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_33/update/.calculate_directory @@ -0,0 +1,3 @@ +{% calculate append = 'skip', action = 'update', +package = 'test-category/test-package' %} +{% calculate path = '/etc' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/.calculate_directory new file mode 100644 index 0000000..6390c76 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/file_0 b/tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/file_0 new file mode 100644 index 0000000..ba8de1e --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_33/update/dir_35/file_0 @@ -0,0 +1,5 @@ +{% calculate append = 'join', format = 'bind' -%} +options { + parameter-1 {{ variables.variable_1 }}; + parameter-2 {{ variables.variable_2 }}; +}; diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_34/install/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_34/install/.calculate_directory new file mode 100644 index 0000000..da1e1b6 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_34/install/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', action = 'install' %} +{% calculate path = '/etc' %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/.calculate_directory b/tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/.calculate_directory new file mode 100644 index 0000000..9c40f92 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', +package = ['test-category/test-package', 'test-category/other-package'] %} diff --git a/tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/file_0 b/tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/file_0 new file mode 100644 index 0000000..d253f96 --- /dev/null +++ b/tests/templates/testfiles/test_dir_processor_root/templates_34/install/dir_36/file_0 @@ -0,0 +1 @@ +{% calculate merge = 'test-category/test-package' -%} diff --git a/tests/templates/testfiles/test_runner/install/.calculate_directory b/tests/templates/testfiles/test_runner/install/.calculate_directory new file mode 100644 index 0000000..353f912 --- /dev/null +++ b/tests/templates/testfiles/test_runner/install/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', action = 'install' %} +{% calculate path = '/home/divanov/Home/protected/test_dir' %} diff --git a/tests/templates/testfiles/test_runner/install/dir_2/.calculate_directory b/tests/templates/testfiles/test_runner/install/dir_2/.calculate_directory new file mode 100644 index 0000000..5b6e8ac --- /dev/null +++ b/tests/templates/testfiles/test_runner/install/dir_2/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join', package = 'app-editors/nano' %} diff --git a/tests/templates/testfiles/test_runner/install/dir_2/file_0 b/tests/templates/testfiles/test_runner/install/dir_2/file_0 new file mode 100644 index 0000000..e48cbd7 --- /dev/null +++ b/tests/templates/testfiles/test_runner/install/dir_2/file_0 @@ -0,0 +1,5 @@ +{% calculate append = 'join', format = 'bind' -%} +options { + cl_chroot_path {{ main.cl_chroot_path }}; + make_profiles {{ os.gentoo.make_profile }}; +}; diff --git a/tests/templates/testfiles/test_runner/install/dir_3/.calculate_directory b/tests/templates/testfiles/test_runner/install/dir_3/.calculate_directory new file mode 100644 index 0000000..8e3df7f --- /dev/null +++ b/tests/templates/testfiles/test_runner/install/dir_3/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'skip' %} diff --git a/tests/templates/testfiles/test_runner/install/dir_3/file_0 b/tests/templates/testfiles/test_runner/install/dir_3/file_0 new file mode 100644 index 0000000..00a9f5d --- /dev/null +++ b/tests/templates/testfiles/test_runner/install/dir_3/file_0 @@ -0,0 +1 @@ +{% calculate merge = 'app-editors/vim' -%} diff --git a/tests/templates/testfiles/test_runner/remove/.calculate_directory b/tests/templates/testfiles/test_runner/remove/.calculate_directory new file mode 100644 index 0000000..d360ee2 --- /dev/null +++ b/tests/templates/testfiles/test_runner/remove/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', action = 'remove' %} +{% calculate path = '/home/divanov/Home/protected' %} diff --git a/tests/templates/testfiles/test_runner/remove/test_dir/.calculate_directory b/tests/templates/testfiles/test_runner/remove/test_dir/.calculate_directory new file mode 100644 index 0000000..b37eeb7 --- /dev/null +++ b/tests/templates/testfiles/test_runner/remove/test_dir/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'remove', package = 'app-editors/nano' %} diff --git a/tests/templates/testfiles/test_runner/update/.calculate_directory b/tests/templates/testfiles/test_runner/update/.calculate_directory new file mode 100644 index 0000000..0e91b87 --- /dev/null +++ b/tests/templates/testfiles/test_runner/update/.calculate_directory @@ -0,0 +1,2 @@ +{% calculate append = 'skip', action = 'update' %} +{% calculate path = '/home/divanov/Home/protected/test_dir' %} diff --git a/tests/templates/testfiles/test_runner/update/dir_1/.calculate_directory b/tests/templates/testfiles/test_runner/update/dir_1/.calculate_directory new file mode 100644 index 0000000..f52207e --- /dev/null +++ b/tests/templates/testfiles/test_runner/update/dir_1/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join', package = 'app-editors/vim' %} diff --git a/tests/templates/testfiles/test_runner/update/dir_1/file_0 b/tests/templates/testfiles/test_runner/update/dir_1/file_0 new file mode 100644 index 0000000..2c0e189 --- /dev/null +++ b/tests/templates/testfiles/test_runner/update/dir_1/file_0 @@ -0,0 +1,4 @@ +{% calculate append = 'join', format = 'bind' -%} +options { + vim_version {{ pkg() }}; +}; diff --git a/tests/templates/testfiles/test_runner/update/dir_2/.calculate_directory b/tests/templates/testfiles/test_runner/update/dir_2/.calculate_directory new file mode 100644 index 0000000..5b6e8ac --- /dev/null +++ b/tests/templates/testfiles/test_runner/update/dir_2/.calculate_directory @@ -0,0 +1 @@ +{% calculate append = 'join', package = 'app-editors/nano' %} diff --git a/tests/templates/testfiles/test_runner/update/dir_2/file_0 b/tests/templates/testfiles/test_runner/update/dir_2/file_0 new file mode 100644 index 0000000..c1e6f7b --- /dev/null +++ b/tests/templates/testfiles/test_runner/update/dir_2/file_0 @@ -0,0 +1,4 @@ +{% calculate append = 'join', format = 'bind' -%} +options { + cl_ignore_files {{ main.cl_ignore_files | join(', ') }}; +}; diff --git a/tests/utils/test_files.py b/tests/utils/test_files.py index e93ed82..2491cf8 100644 --- a/tests/utils/test_files.py +++ b/tests/utils/test_files.py @@ -37,7 +37,7 @@ class TestUtils(): result = pipe_result.stdout.decode().strip() assert output == result - def test_if_a_pipe_Process_object_uses_for_several_writes_without_readings__it_successfully_executes(self): + def test_if_a_pipe_Process_object_is_used_for_several_writes_without_readings__it_successfully_executes(self): input_lines = ['KEY: important line.\n', 'NONE: not important line.\n', 'NONE: nothing interesting.\n', @@ -58,7 +58,7 @@ class TestUtils(): output = pipe.read() assert output == output_text - def test_if_a_pipe_Process_object_uses_for_several_writes__it_successfully_executes_even_after_read(self): + def test_if_a_pipe_Process_object_is_used_for_several_writes__it_successfully_executes_even_after_read(self): input_lines = ['KEY: important line.\n', 'NONE: not important line.\n', 'NONE: nothing interesting.\n', diff --git a/tests/variables/old_vars/old_vars.py b/tests/variables/old_vars/old_vars.py deleted file mode 100644 index 547d279..0000000 --- a/tests/variables/old_vars/old_vars.py +++ /dev/null @@ -1,793 +0,0 @@ -import pytest -import os -from calculate.variables.old_vars.datavars import Namespace, Variable,\ - CyclicVariableError,\ - VariableError,\ - ReadonlyVariable,\ - ChoiceVariable,\ - IntegerVariable,\ - StringVariable,\ - DefaultValue,\ - TableVariable, HashVariable,\ - VariableNotFoundError -from calculate.variables.old_vars.vars_loader import NamespaceIniFiller,\ - VariableLoader,\ - ProfileFiller,\ - NamespaceIniFillerStrict - - -@pytest.mark.old_vars -class TestNamespace: - def test_init_empty_namespace(self): - ns = Namespace() - assert ns.childs == {} - assert ns.variables == {} - - def test_create_variable(self): - ns = Namespace() - ns.add_string_variable("test", "12345") - assert ns.test.get_value() == "12345" - ns.add_string_variable("zxcv", "23456") - assert ns.zxcv.get_value() == "23456" - - def test_create_ns_with_vars(self): - ns = Namespace() - ns.add_namespace(Namespace("os")) - ns.os.add_string_variable("test", "123") - ns.os.add_namespace(Namespace("linux")) - ns.os.linux.add_string_variable("shortname", "CLD") - assert ns.os.test.get_value() == "123" - assert ns.os["test"].get_value() == "123" - assert ns.os.linux.shortname.get_value() == "CLD" - assert ns.os.root == ns - - def test_fill_namespace_simple(self): - ns = Namespace() - nsif = NamespaceIniFiller() - nsif.fill(ns, """ - [os] - test = 123 - - [os][linux] - shortname = CLD - fullname = Calculate Linux Desktop - """) - - assert ns.os.test.get_value() == "123" - assert ns.os.linux.shortname.get_value() == "CLD" - assert ns.os.linux.fullname.get_value() == "Calculate Linux Desktop" - assert ns.os.linux.root == ns - - nsif.fill(ns, """ - [os][linux] - shortname = CLDX - """) - - assert ns.os.linux.shortname.get_value() == "CLDX" - assert ns.os.linux.fullname.get_value() == "Calculate Linux Desktop" - - def test_fill_namespace_append_and_remove(self): - ns = Namespace() - nsif = NamespaceIniFiller() - nsif.fill(ns, """ - [os] - test = 123 - - [os] - test += 345 - """) - - assert ns.os.test.get_value() == "123,345" - - nsif.fill(ns, """ - [os] - test -= 123 - """) - - assert ns.os.test.get_value() == "345" - - nsif.fill(ns, """ - [os] - test += asdf,qwer,zxcv - """) - - assert ns.os.test.get_value() == "345,asdf,qwer,zxcv" - - nsif.fill(ns, """ - [os] - test -= asdf,zxcv - """) - - assert ns.os.test.get_value() == "345,qwer" - - def test_fill_namespace_clear_namespaces(self): - ns = Namespace() - nsif = NamespaceIniFiller() - nsif.fill(ns, """ - [test][0] - dev = /dev/sda1 - mount = swap - - [test][1] - dev = /dev/sda2 - mount = / - - [test][2] - dev = /dev/sda5 - mount = /var/calculate - """) - - assert [x.dev.get_value() for x in ns.test] == ['/dev/sda1', - '/dev/sda2', - '/dev/sda5'] - - nsif.fill(ns, """ - [test][0] - dev = /dev/sdb1 - mount = swap - - [test][1] - dev = /dev/sdb2 - mount = / - """) - - assert [x.dev.get_value() for x in ns.test] == ['/dev/sdb1', - '/dev/sdb2', - '/dev/sda5'] - - nsif.fill(ns, """ - [test][] - - [test][0] - dev = /dev/sdb1 - mount = swap - - [test][1] - dev = /dev/sdb2 - mount = / - """) - - assert [x.dev.get_value() for x in ns.test] == ['/dev/sdb1', - '/dev/sdb2'] - - def test_fill_namespace_strict_clear(self): - ns = Namespace() - nsif = NamespaceIniFiller() - nsif.fill(ns, """ - [os][test][0] - dev = /dev/sda1 - mount = swap - - [os][test][1] - dev = /dev/sda2 - mount = / - - [os][test][2] - dev = /dev/sda5 - mount = /var/calculate - - [custom][test] - zxcv = 1 - - [custom][test2] - zxcv = 2 - """) - - assert ns.custom.test.zxcv.get_value() == "1" - assert ns.custom.test2.zxcv.get_value() == "2" - - nsifs = NamespaceIniFillerStrict() - nsifs.fill(ns, """ - [os][test][] - - [custom][] - """) - - assert [x for x in ns.custom] == [] - - def test_get_namespace_attrs(self): - ns = Namespace() - os = Namespace("os") - os.add_string_variable("test", "zxcv") - ns.add_namespace(os) - - assert ns.os.test.get_value() == "zxcv" - assert ns.os["test"].get_value() == "zxcv" - assert "test" in ns.os - ns.os.test.set_value("123") - assert ns.os.test.get_value() == "123" - ns.os["test"].set_value("234") - assert ns.os.test.get_value() == "234" - - def test_variable_get_value(self): - class TestVar(Variable): - def get(self): - return "A" - - var = TestVar("test") - - assert var.get_value() == "A" - - def test_namespace_lookup(self): - ns = Namespace() - os = Namespace("os") - device = Namespace("device") - linux = Namespace("linux") - ns.add_namespace(os) - os.add_namespace(linux) - os.add_namespace(device) - device1 = device.add_namespace() - device1.add_string_variable("dev", "/dev/sda") - device2 = device.add_namespace() - device2.add_string_variable("dev", "/dev/sdb") - device3 = device.add_namespace() - device3.add_string_variable("dev", "/dev/sdc") - - ns.add_string_variable("first", "first") - os.add_string_variable("second", "second") - linux.add_string_variable("third", "third") - - assert ns.first.get_value() == "first" - assert ns.root.os.second.get_value() == "second" - assert ns.os.second.get_value() == "second" - assert os.root.os.second.get_value() == "second" - assert os.second.get_value() == "second" - assert linux.third.get_value() == "third" - assert linux.root.os.second.get_value() == "second" - - with pytest.raises(VariableNotFoundError): - os.third - - assert ns.os.device[0].dev.get_value() == "/dev/sda" - assert ns.os.device["0"].dev.get_value() == "/dev/sda" - assert ns.os.device[1].dev.get_value() == "/dev/sdb" - assert ns.os.device[2].dev.get_value() == "/dev/sdc" - - assert ns.os.device.parent.second.get_value() == "second" - assert ns.os.device.parent.parent.os.second.get_value() == "second" - assert ns.os.device.parent.parent.parent.os.second.\ - get_value() == "second" - - def test_variable_get_value_by_variable(self): - class TestVar1(Variable): - def get(self): - return "%s,B" % self.vars.test2.get_value(self) - - class TestVar2(Variable): - def get(self): - return "A" - - ns = Namespace() - ns.add_variable(TestVar1("test1")) - ns.add_variable(TestVar2("test2")) - - assert ns.test1.get_value() == "A,B" - - def test_variable_get_value_by_changed_variable(self): - class TestVar1(Variable): - counter = 0 - - def get(self): - self.counter += 1 - return "%s,B" % self.vars.test2.get_value(self) - - ns = Namespace() - test1 = TestVar1("test1") - ns.add_variable(test1) - ns.add_string_variable("test2", "A") - - assert ns.test1.get_value() == "A,B" - - assert test1.counter == 1 - # test for get cached variable value - assert ns.test1.get_value() == "A,B" - assert test1.counter == 1 - - # change value of test2 for recalculate test1 - ns.test2.set_value("C") - assert ns.test1.get_value() == "C,B" - assert test1.counter == 2 - - # change value of test2 for recalculate test1 - ns.test2.set_value("D") - assert ns.test1.get_value() == "D,B" - assert test1.counter == 3 - - def test_cyclic_variable(self): - class TestVar1(Variable): - def get(self): - return "%s,test1" % self.vars.test2.get_value(self) - - class TestVar2(Variable): - def get(self): - return "%s,test2" % self.vars.test3.get_value(self) - - class TestVar3(Variable): - def get(self): - return "%s,test3" % self.vars.test1.get_value(self) - - test1 = TestVar1("test1") - test2 = TestVar2("test2") - test3 = TestVar3("test3") - - ns = Namespace() - ns.add_variable(test1) - ns.add_variable(test2) - ns.add_variable(test3) - - with pytest.raises(CyclicVariableError) as e: - ns.test1.get_value() - - assert e.value.queue[:-1] == ("test1", "test2", "test3") - - with pytest.raises(VariableError) as e: - ns.test1.get_value() - - def test_drop_invalidate_after_set_value(self): - class TestVar1(Variable): - counter = 0 - - def get(self): - self.counter += 1 - return "%s,test1" % self.vars.test2.get_value(self) - - class TestVar2(Variable): - def get(self): - return "ZZZZ" - - test1 = TestVar1("test1") - test2 = TestVar2("test2") - ns = Namespace() - ns.add_variable(test1) - ns.add_variable(test2) - - assert test1.get_value() == "ZZZZ,test1" - - test1.set_value("VVVV") - - assert test1.get_value() == "VVVV" - assert test1.counter == 1 - - test2.set_value("XXXX") - - assert test1.get_value() == "VVVV" - assert test1.counter == 1 - - test1.invalidate() - - assert test1.get_value() == "XXXX,test1" - assert test1.counter == 2 - - def test_change_invalidator_variable(self): - class VarTest(Variable): - counter = 0 - - def get(self): - self.counter += 1 - - if self.vars.ifvar.get_value(self): - return "%s,test1" % self.vars.vara.get_value(self) - else: - return "%s,test1" % self.vars.varb.get_value(self) - - vartest = VarTest("vartest") - ns = Namespace() - ns.add_variable(vartest) - ns.add_string_variable("vara", "vara") - ns.add_string_variable("varb", "varb") - ns.add_string_variable("ifvar", "true") - - assert vartest.get_value() == "vara,test1" - assert vartest.counter == 1 - - ns.vara.set_value("varc") - assert vartest.get_value() == "varc,test1" - assert vartest.counter == 2 - - ns.ifvar.set_value("") - assert vartest.get_value() == "varb,test1" - assert vartest.counter == 3 - - ns.vara.set_value("vard") - assert vartest.get_value() == "varb,test1" - assert vartest.counter == 3 - - def test_readonly_varaible(self): - class TestVar1(Variable): - properties = [ReadonlyVariable, StringVariable] - - def get(self): - return "test1" - - test1 = TestVar1("test1") - assert test1.get_value() == "test1" - - with pytest.raises(VariableError): - test1.set_value("test2") - - assert test1.get_value() == "test1" - test1.set_value("test2", force=True) - assert test1.get_value() == "test2" - - def test_choice_variable(self): - class TestVar1(Variable): - properties = [ChoiceVariable] - - def choice(self): - return ["test1", "test2"] - - test1 = TestVar1("test1") - with pytest.raises(VariableError): - test1.set_value("test3") - test1.set_value("test2") - assert test1.get_value() == "test2" - - def test_integer_variable(self): - class TestVar1(Variable): - properties = [IntegerVariable] - - test1 = TestVar1("test1") - with pytest.raises(VariableError): - test1.set_value("test3") - test1.set_value("33") - assert test1.get_value() == 33 - test1.set_value("-33") - assert test1.get_value() == -33 - - def test_default_value_property(self): - class TestVar1(Variable): - def get(self): - return "123" - - test1 = TestVar1("test1") - assert test1.get_value() == "123" - - test1.set_value("987") - test1.add_property(DefaultValue("567")) - - assert test1.get_value() == "987" - test1.invalidate() - assert test1.get_value() == "567" - - def test_get_comment(self): - class TestVar1(Variable): - def get(self): - return "123" - - class TestVar2(Variable): - def get(self): - return "234" - - def get_comment(self): - return "[%s]" % self.get_value() - - class TestVar3(Variable): - def get(self): - return "ZXC %s" % self.vars.test2.get_comment_value(self) - - ns = Namespace() - test1 = TestVar1("test1") - assert test1.get_value() == "123" - assert test1.get_comment() == "123" - - test2 = TestVar2("test2") - assert test2.get_value() == "234" - assert test2.get_comment() == "[234]" - - test3 = TestVar3("test3") - ns.add_variable(test1) - ns.add_variable(test2) - ns.add_variable(test3) - - assert test3.get_value() == "ZXC [234]" - test2.set_value("567") - assert test3.get_value() == "ZXC [567]" - - def test_wrong_choice_varaible(self): - class TestVar1(Variable): - properties = [ChoiceVariable] - - class TestVar2(Variable): - properties = [ChoiceVariable] - - def choice(self): - return ["test1", "test2"] - - class TestVar3(Variable): - properties = [ChoiceVariable] - - def choice_comment(self): - return [("test1", "Test1"), - ("test2", "Test2")] - - test1 = TestVar1("test1") - test2 = TestVar2("test2") - test3 = TestVar3("test3") - - with pytest.raises(VariableError): - test1.choice() - - with pytest.raises(VariableError): - test1.choice_comment() - - assert test2.choice() == ["test1", "test2"] - assert test2.choice_comment() == [("test1", "test1"), - ("test2", "test2")] - - assert test3.choice() == ["test1", "test2"] - assert test3.choice_comment() == [("test1", "Test1"), - ("test2", "Test2")] - - def test_loading_test_variable_module(self): - ns = Namespace() - vl = VariableLoader() - vl.fill(ns, "tests/vars/variables", "testvars") - - assert ns.level.simple.get_value() == "simple value" - assert ns.level.uselocalsimple.get_value() == "Using simple value" - assert ns.level.usefullsimple.get_value() == "Using simple value" - - with pytest.raises(VariableError): - ns.level.badchoice.choice() - - with pytest.raises(VariableError): - ns.level.badchoice.choice_comment() - - assert ns.level.simple_choice.choice() == ["/dev/sda1", - "/dev/sda2", - "/dev/sda3"] - assert ns.level.comment_choice.choice() == ["/dev/sda1", - "/dev/sda2", - "/dev/sda3"] - assert ns.level.comment_choice.choice_comment() == [ - ("/dev/sda1", "SWAP"), - ("/dev/sda2", "ROOT"), - ("/dev/sda3", "DATA")] - - ns.level.disks.set_value(["/dev/sda2", "/dev/sda1"]) - - assert ns.level.disks.get_value() == ["/dev/sda2", "/dev/sda1"] - - assert ns.level.comment_choice.choice() == ["/dev/sda2", "/dev/sda1"] - assert ns.level.comment_choice.choice_comment() == [ - ("/dev/sda2", "ROOT"), - ("/dev/sda1", "SWAP")] - - assert ns.level is not ns.level.level2.root - assert ns is ns.level.level2.root - assert ns.level.level2.vargetter.get_value() == "/ test" - - def test_hash_variable(self): - # hash variable - ns = Namespace() - vl = VariableLoader() - vl.fill(ns, "tests/vars/variables", "testvars") - - assert ns.level.linux.get_fullname() == "level.linux" - assert ns.level.linux.ver.fullname == "level.linux.ver" - - assert ns.level.linux.ver.get_value() == "1.0" - assert ns.level.linux.shortname.get_value() == "CLD" - - # проверка обновления значения hash переменной при обновлении - # значения у зависимой перемнной - ns.level.version.set_value("2.0") - assert ns.level.linux.ver.get_value() == "2.0" - - # проверка установки значения hash переменной - ns.level.linux.ver.set_value("3.0") - assert ns.level.linux.ver.get_value() == "3.0" - - # после установки хотя бы одного значения в hash переменной - # обновление остальных прекращаются до инвалидации (так как - # значения рассматриваются комплексно) - ns.level.myshortname.set_value("CLDG") - assert ns.level.linux.shortname.get_value() == "CLD" - - # проверка попытки изменить readonly переменную - with pytest.raises(VariableError): - ns.level.linux.shortname.set_value("CLDX") - - # проверка сбора значения hash перемнной - ns.level.linux.invalidate() - assert ns.level.linux.ver.get_value() == "2.0" - assert ns.level.linux.shortname.get_value() == "CLDG" - - assert ns.level.linux.test.get_value() == "my test - 2.0" - - # проверка обновления значения переменной, используеющей одно - # из значений hash переменной - assert ns.level.shortname_test.get_value() == "CLDG test" - ns.level.linux.shortname.set_value("CLDX", force=True) - assert ns.level.shortname_test.get_value() == "CLDX test" - - def test_table_variable(self): - # table variable - ns = Namespace() - vl = VariableLoader() - vl.fill(ns, "tests/vars/variables", "testvars") - - assert ns.level.device[0].dev.get_value() == "/dev/sda" - assert ns.level.device[1].dev.get_value() == "/dev/sdb" - assert ns.level.device.get_value() == [{"dev": "/dev/sda", - "type": "hdd", - "name": "Samsung SSD"}, - {"dev": "/dev/sdb", - "type": "flash", - "name": "Transcend 64GB"}] - assert ns.level.device[1].type.get_value() == "flash" - - # проверка обновления списка пространства имён у табличной переменной - ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb", "/dev/sdc"]) - assert ns.level.device[2].type.get_value() == "usbhdd" - assert [x.type.get_value() for x in ns.level.device] == ["hdd", - "flash", - "usbhdd"] - - assert ns.level.device_child.get_value() == "hdd" - ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb"]) - ns.level.device[0].type.set_value("flash") - assert ns.level.device.get_value() == [{"dev": "/dev/sda", - "type": "flash", - "name": "Samsung SSD"}, - {"dev": "/dev/sdb", - "type": "flash", - "name": "Transcend 64GB"}] - assert ns.level.device_child.get_value() == "flash" - - # после установки хотя бы одного значения в table переменной - # обновление остальных прекращаются до инвалидации (так как - # значения рассматриваются комплексно) - ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb", "/dev/sdc"]) - assert [x.dev.get_value() for x in ns.level.device] == ["/dev/sda", - "/dev/sdb"] - ns.level.device.invalidate() - assert [x.dev.get_value() for x in ns.level.device] == ["/dev/sda", - "/dev/sdb", - "/dev/sdc"] - # проверить на повторное изменение, убедится, что _drop_child - # отрабатывает - ns.level.devicelist.set_value(["/dev/sda", "/dev/sdb", - "/dev/sdc", "/dev/sdd"]) - ns.level.device.invalidate() - assert [x.dev.get_value() for x in ns.level.device] == ["/dev/sda", - "/dev/sdb", - "/dev/sdc", - "/dev/sdd"] - - def test_wrong_table_variable(self): - class Testtable(TableVariable): - class Data(TableVariable.Data): - def get(self): - return [{'dev': '123', 'name': '098'}] - - ns = Namespace() - ns.add_namespace(Namespace("test")) - ns.test.add_namespace(Namespace("test2")) - - error_message = ("Missed 'hash_vars' attribute for table " - "variable test.test2.testtable") - with pytest.raises(VariableError) as e: - ns.test.test2.add_namespace(Testtable("testtable", ns.test.test2)) - assert str(e.value) == error_message - - def test_wrong_hash_variable(self): - class Testhash(HashVariable): - class Data(HashVariable.Data): - def get(self): - return {'dev': '123', 'name': '098'} - - ns = Namespace() - ns.add_namespace(Namespace("test")) - ns.test.add_namespace(Namespace("test2")) - - error_message = ("Missed 'hash_vars' attribute for hash " - "variable test.test2.testhash") - with pytest.raises(VariableError) as e: - ns.test.test2.add_namespace(Testhash("testhash", ns.test.test2)) - assert str(e.value) == error_message - - def test_namespace_iteration(self): - ns = Namespace() - ns0 = ns.add_namespace() - ns0.add_string_variable("test", "123") - ns1 = ns.add_namespace() - ns1.add_string_variable("test", "234") - ns2 = ns.add_namespace() - ns2.add_string_variable("test", "456") - - assert [x.test.get_value() for x in ns] == ["123", "234", "456"] - - def test_subnamespace(self): - ns = Namespace() - vl = VariableLoader() - vl.fill(ns, "tests/vars/variables", "testvars") - - assert ns.level.level3.my_var1.get_value() == "testing" - assert ns.level.level3.myvar2.get_value() == "testing2" - - def test_variable_not_found(self): - ns = Namespace() - vl = VariableLoader() - vl.fill(ns, "tests/vars/variables", "testvars") - - error_message = "Variable or namespace level.level3.myvar3 not found" - with pytest.raises(VariableError) as e: - ns.level.level3.myvar3.get_value() - assert str(e.value) == error_message - - # TODO: тест использует значения на конкретной машине - # def test_simple(self): - # ns = Namespace() - # vl = VariableLoader() - # vl.fill(ns, *VariableLoader.default()) - # # нужно исправить тест, так - # # assert [x.name.get_value() for x in ns.os.gentoo.repositories] ==\ - # ["gentoo","distros","calculate","custom"] - # assert ns.os.gentoo.make_profile.get_value() ==\ - # "/etc/portage/make.profile" - # assert ns.os.gentoo.profile.path.get_value() ==\ - # "/var/db/repos/distros/profiles/CLD/amd64/20" - # assert ns.os.gentoo.profile.name.get_value() ==\ - # "distros:CLD/amd64/20" - - def test_profile_filler(self): - ns = Namespace() - vl = VariableLoader() - vl.fill(ns, "tests/vars/variables", "testvars") - - class ProfileFillerTest(ProfileFiller): - def get_repository_map(self, ns): - curdir = os.getcwd() - return {'distros': - os.path.join(curdir, - "tests/utils/gentoo/repos/distros"), - 'calculate': - os.path.join(curdir, - "tests/utils/gentoo/repos/calculate"), - 'gentoo': - os.path.join(curdir, - "tests/utils/gentoo/portage")} - - assert ns.os.hashvar.value1.get_value() == "test1" - assert ns.os.hashvar.value2.get_value() == "test2" - - assert ns.os.tablevar[0].dev.get_value() == "/dev/sdb1" - assert ns.os.tablevar[1].dev.get_value() == "/dev/sdb2" - - pf = ProfileFillerTest() - pf.fill(ns, "tests/utils/gentoo/repos/distros/profiles/CLD/amd64") - - assert ns.os.linux.test.get_value() == "test" - assert ns.os.linux.arch.get_value() == "amd64" - - assert ns.os.linux.title.get_value() ==\ - "Calculate Linux Desktop KDE 20" - - # Hash - assert ns.os.hashvar.value1.get_value() == "20" - assert ns.os.hashvar.value2.get_value() == "30" - - ns.os.hashvar.value1.set_value("40") - assert ns.os.hashvar.value1.get_value() == "40" - ns.os.hashvar.value1.invalidate() - assert ns.os.hashvar.value1.get_value() == "20" - - # Table - assert ns.os.tablevar[0].dev.get_value() == "/dev/sda1" - assert ns.os.tablevar[1].dev.get_value() == "/dev/sda2" - assert ns.os.tablevar[2].dev.get_value() == "/dev/sda5" - ns.os.tablevar[0].dev.invalidate() - assert ns.os.tablevar[0].dev.get_value() == "/dev/sda1" - assert ns.os.tablevar[1].dev.get_value() == "/dev/sda2" - assert ns.os.tablevar[2].dev.get_value() == "/dev/sda5" - - def test_fill_namespace_by_module(self): - pass - # ns = Namespace() - # vl = VariableLoader() - # vl.fill(ns, *VariableLoader.default()) - # assert "os" in ns - # assert "config" in ns.os.gentoo - # assert "main" in ns - # assert ns.os.gentoo.config.get_value() is not None diff --git a/tests/variables/old_vars/variables/level/__init__.py b/tests/variables/old_vars/variables/level/__init__.py deleted file mode 100644 index 9ab6d7e..0000000 --- a/tests/variables/old_vars/variables/level/__init__.py +++ /dev/null @@ -1,119 +0,0 @@ -from calculate.vars.datavars import Namespace, Variable, ChoiceVariable,\ - ReadonlyVariable, ListVariable,\ - HashVariable, TableVariable, ListVariable - - -class Simple(Variable): - def get(self): - return "simple value" - - -class Uselocalsimple(Variable): - def get(self): - return "Using %s" % self.vars.simple.get_value(self) - - -class Usefullsimple(Variable): - def get(self): - return "Using %s" % self.vars.root.level.simple.get_value(self) - - -class Badchoice(Variable): - properties = [ChoiceVariable] - - -class Disks(Variable): - properties = [ListVariable] - - def get(self): - return ["/dev/sda1", "/dev/sda2", "/dev/sda3"] - - def get_comment(self): - mymap = {'/dev/sda1': 'SWAP', - '/dev/sda2': 'ROOT', - '/dev/sda3': 'DATA'} - return [mymap.get(x) for x in self.get_value()] - - -class SimpleChoice(Variable): - properties = [ChoiceVariable] - - def choice(self): - return self.vars.disks.get_value(self) - - -class CommentChoice(Variable): - properties = [ChoiceVariable] - - def choice_comment(self): - return list(zip(self.vars.disks.get_value(), - self.vars.disks.get_comment())) - - -class Version(Variable): - value = "1.0" - - -class Myshortname(Variable): - value = "CLD" - - -class Linux(HashVariable): - class Data(HashVariable.Data): - def get(self): - return { - 'ver': self.vars.version.get_value(self), - 'shortname': self.vars.myshortname.get_value(self) - } - - class Test(Variable): - def get(self): - return "my test - %s" % self.vars.ver.get_value(self) - - readonly_vars = ['shortname'] - hash_vars = ["ver", "shortname"] - - -class ShortnameTest(Variable): - def get(self): - return "{} test".format(self.vars.linux.shortname.get_value(self)) - - -class Devicelist(Variable): - properties = [ListVariable] - - def get(self): - return ["/dev/sda", "/dev/sdb"] - - -class Device(TableVariable): - class Data(TableVariable.Data): - def get(self): - mapData = {'/dev/sda': ["hdd", "Samsung SSD"], - '/dev/sdb': ["flash", "Transcend 64GB"], - '/dev/sdc': ["usbhdd", "WD 1TB"]} - defaultValue = ["hdd", "Unknown"] - return [ - {"dev": x, - "type": mapData.get(x, defaultValue)[0], - "name": mapData.get(x, defaultValue)[1]} - for x in self.vars.devicelist.get_value(self) - ] - - hash_vars = ["dev", "type", "name"] - readonly_vars = ["name"] - - -class DeviceChild(Variable): - def get(self): - return self.vars.device[0].type.get_value(self) - - -class Level3(Namespace): - class MyVar1(Variable): - def get(self): - return "testing" - - class Myvar2(Variable): - def get(self): - return "testing2" diff --git a/tests/variables/old_vars/variables/level/level2/__init__.py b/tests/variables/old_vars/variables/level/level2/__init__.py deleted file mode 100644 index f9d260a..0000000 --- a/tests/variables/old_vars/variables/level/level2/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from calculate.vars.datavars import Variable - - -class Vargetter(Variable): - def get(self): - return "{} test".format(self.vars.root.main.chroot.get_value(self)) diff --git a/tests/variables/old_vars/variables/main/__init__.py b/tests/variables/old_vars/variables/main/__init__.py deleted file mode 100644 index 70f5e0b..0000000 --- a/tests/variables/old_vars/variables/main/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from calculate.vars.datavars import Variable, ReadonlyVariable - - -class Chroot(Variable): - properties = [ReadonlyVariable] - value = "/" diff --git a/tests/variables/old_vars/variables/os/__init__.py b/tests/variables/old_vars/variables/os/__init__.py deleted file mode 100644 index 2a57c00..0000000 --- a/tests/variables/old_vars/variables/os/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -from calculate.vars.datavars import Variable, Namespace, HashVariable, TableVariable - - -class Linux(Namespace): - class Shortname(Variable): - value = "" - - class Ver(Variable): - value = "" - - class Fullname(Variable): - value = "" - - class Subname(Variable): - value = "" - - class Arch(Variable): - value = "" - - class Test(Variable): - value = "" - - class Title(Variable): - def get(self): - subname = self.vars.subname.get_value(self) - fullname = self.vars.fullname.get_value(self) - ver = self.vars.ver.get_value(self) - if subname: - return "%s %s %s" % (fullname, subname, ver) - else: - return "%s %s" % (fullname, ver) - - -class Hashvar(HashVariable): - class Data(HashVariable.Data): - def get(self): - return { - 'value1': 'test1', - 'value2': 'test2', - } - - class Calculate(Variable): - def get(self): - return "%s %s" % ( - self.vars.value1.get_value(self), - self.vars.value2.get_value(self) - ) - - hash_vars = ["value1", "value2"] - - -class Tablevar(TableVariable): - class Data(TableVariable.Data): - def get(self): - return [ - { - "dev": "/dev/sdb1", - "mount": "/", - }, - { - "dev": "/dev/sdb2", - "mount": "/var/calculate", - } - ] - hash_vars = ["dev", "mount"] diff --git a/tests/variables/test_calculateini.py b/tests/variables/test_calculateini.py index 7c9d9bb..7b1986e 100644 --- a/tests/variables/test_calculateini.py +++ b/tests/variables/test_calculateini.py @@ -4,11 +4,6 @@ from calculate.variables.loader import CalculateIniParser, Define @pytest.mark.vars class TestCalculateIni: - def test_empty_calculate_ini(self): - pass - # ns = Namespace(varPath=None) - # assert ns.varPath is None - def test_section_values(self): ini_parser = CalculateIniParser() diff --git a/tests/variables/test_variables.py b/tests/variables/test_datavars.py similarity index 84% rename from tests/variables/test_variables.py rename to tests/variables/test_datavars.py index 8884ba3..c47f51a 100644 --- a/tests/variables/test_variables.py +++ b/tests/variables/test_datavars.py @@ -8,8 +8,10 @@ from calculate.variables.datavars import NamespaceNode, VariableNode,\ VariableType, VariableError,\ TableType, BooleanType,\ VariableTypeError, FloatType,\ - ListType -from calculate.templates.template_engine import TemplateEngine, FILE + ListType, VariableNotFoundError,\ + Copy, Format, Calculate, Static +from calculate.templates.template_engine import TemplateEngine, FILE,\ + Variables, DIR from calculate.templates.template_processor import TemplateExecutor from calculate.variables.loader import NamespaceIniFiller, Datavars from calculate.utils.files import stderr_devnull, read_file @@ -199,7 +201,7 @@ class TestDatavars: Variable('var_1', source='val_1', type=StringType) with Namespace('namespace_2'): - assert Dependence._find_variable('namespace_1.var_1') ==\ + assert Dependence._get_variable('namespace_1.var_1') ==\ datavars.namespace_1['var_1'] def test_if_dependence_interface_object_is_used_for_finding_an_unexisting_variable_from_an_other_namespace_using_absolute_variable_name__the_find_variable_method_raises_the_VariableError_exception(self): @@ -210,7 +212,7 @@ class TestDatavars: with Namespace('namespace_2'): with pytest.raises(VariableError): - Dependence._find_variable('namespace_1.var_2') + Dependence._get_variable('namespace_1.var_2') def test_if_dependence_interface_object_is_used_for_finding_an_existing_variable_from_a_same_namespace_using_relative_variable_name__the_find_variable_method_returns_the_desired_variable(self): Namespace.reset() @@ -224,9 +226,9 @@ class TestDatavars: with Namespace('namespace_1_1'): Variable('var_1', source='val_1', type=StringType) - assert Dependence._find_variable('.var_1') ==\ + assert Dependence._get_variable('.var_1') ==\ datavars.namespace_1.namespace_1_1['var_1'] - assert Dependence._find_variable('..var_3') ==\ + assert Dependence._get_variable('..var_3') ==\ datavars.namespace_1['var_3'] def test_if_dependence_interface_object_is_used_for_finding_an_unexisting_variable_from_a_same_namespace_using_relative_variable_name__the_find_variable_method_raises_the_VariableError_exception(self): @@ -238,7 +240,7 @@ class TestDatavars: with Namespace('namespace_1_1'): with pytest.raises(VariableError): - Dependence._find_variable('..var_3') + Dependence._get_variable('..var_3') def test_if_variable_is_created_with_dependence_in_its_source_and_the_dependence_has_only_set_function_without_any_arguments__the_variable_updates_its_value_using_only_set_function(self): Namespace.reset() @@ -252,6 +254,84 @@ class TestDatavars: assert datavars.namespace_1.var == 'value_from_function' + def test_if_a_variable_is_created_using_a_Copy_of_a_variable_in_the_source__the_new_variable_uses_this_variable_s_value_to_create_its_own_value(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('namespace_1'): + Variable('var_1', source='value_1', type=StringType) + Variable('var_2', type=StringType, source=Copy('.var_1')) + + assert datavars.namespace_1.var_2 == 'value_1' + + def test_if_a_variable_is_created_using_a_Format_with_a_format_string_and_a_variable_in_the_source__the_new_variable_uses_the_variable_and_format_string_to_produce_a_new_string_value(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('namespace_1'): + Variable('var_1', source='value_1', type=StringType) + Variable('var_2', type=StringType, + source=Format('var_1 = { .var_1 }')) + + assert datavars.namespace_1.var_2 == 'var_1 = value_1' + + def test_if_a_variable_is_created_using_a_Calculate_with_a_variable_and_some_function_in_the_source__the_new_variable_uses_the_variable_and_the_function_for_calculating_of_its_value(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('namespace_1'): + Variable('var_1', source='value_1', type=StringType) + + def formatter(var_1): + return 'var_1 = {}'.format(var_1.value) + Variable('var_2', type=StringType, + source=Calculate(formatter, '.var_1')) + + assert datavars.namespace_1.var_2 == 'var_1 = value_1' + + def test_if_a_variable_is_created_using_a_Dependence_with_a_static_value_a_variable_and_depend_function_in_the_source__the_new_variable_uses_the_variable_the_function_and_the_static_value_for_calculating_of_its_value(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('namespace_1'): + def source_function(static_value): + return 'static value = {}'.format(static_value.value) + Variable('var', type=StringType, + source=Dependence(Static('value'), + depend=source_function)) + + assert datavars.namespace_1.var == 'static value = value' + + def test_if_a_variable_is_created_using_a_Calculate_with_a_static_value_a_variable_and_some_function_in_the_source__the_new_variable_uses_the_variable_the_function_and_the_static_value_for_calculating_of_its_value(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('namespace_1'): + Variable('var_1', source='var value', type=StringType) + + def suspicious_function(static_value, var_1): + return '{} and {}'.format(static_value.value, var_1.value) + Variable('var_2', type=StringType, + source=Calculate(suspicious_function, + Static('static value'), '.var_1')) + + assert datavars.namespace_1.var_2 == 'static value and var value' + + def test_if_a_variable_is_created_using_a_Calculate_with_a_non_string_value_a_variable_and_some_function_in_the_source__the_new_variable_uses_the_variable_the_function_and_the_static_value_for_calculating_of_its_value(self): + Namespace.reset() + datavars = Namespace.datavars + + with Namespace('namespace_1'): + Variable('var_1', source=10, type=IntegerType) + + def suspicious_function(static_value, var_1): + return static_value.value + var_1.value + Variable('var_2', type=IntegerType, + source=Calculate(suspicious_function, + 12, '.var_1')) + + assert datavars.namespace_1.var_2 == 22 + def test_if_a_variable_is_created_using_dependence_with_three_variables_and_a_set_function_but_during_the_calculation_only_two_variables_was_used__the_unused_variable_is_not_in_variable_subscriptions_and_is_invalidated_before_first_usage(self): Namespace.reset() datavars = Namespace.datavars @@ -313,7 +393,7 @@ class TestDatavars: assert datavars.namespace_2.var_1 == 'less' datavars.namespace_1['var_1'].source = 5 - assert datavars.namespace_2['var_1'].value is None + assert datavars.namespace_2['var_1']._invalidated assert datavars.namespace_2.var_1 == 'greater' def test_if_variable_was_created_using_variables_api_and_then_was_created_again_with_simple_source_value__the_variable_just_changes_its_source(self): @@ -375,16 +455,6 @@ class TestDatavars: with pytest.raises(CyclicVariableError): datavars.namespace_1.var_3 - def test_if_a_variable_is_created_as_readonly_variable_and_there_is_a_try_to_change_its_source__the_variable_raises_the_VariableError_exception(self): - Namespace.reset() - - with Namespace('namespace_1'): - Variable('var_1', source='value_1', type=StringType.readonly) - - with Namespace('namespace_1'): - with pytest.raises(VariableError): - Variable('var_1', source='value_2', type=StringType) - def test_if_variables_with_string_type_is_created_with_correct_source__the_variable_api_creates_this_variables_and_the_variables_contains_strings(self): Namespace.reset() datavars = Namespace.datavars @@ -817,22 +887,62 @@ varval4 = value4 [section][][sub] [section][][sub][]''' - errors = ['SyntaxError:1: [section', - 'SyntaxError:2: varval1 = value1', - 'SyntaxError:3: varval2 = value2', - ("VariableError:4: can not create namespace '.section'" - " in not 'custom' namespace."), - 'SyntaxError:6: eee', - 'SyntaxError:8: [section2', - ("VariableError:10: can not clear namespace 'section'." - " Namespace is not found."), - 'SyntaxError:11: [section][][sub]', - 'SyntaxError:12: [section][][sub][]'] + if isinstance(datavars, Datavars): + errors = ['SyntaxError:1: [section', + 'SyntaxError:2: varval1 = value1', + 'SyntaxError:3: varval2 = value2', + ("VariableError:4: variables package 'section' is not" + " found."), + 'SyntaxError:6: eee', + 'SyntaxError:8: [section2', + ("VariableError:10: can not clear namespace 'section'." + " Variables package 'section' is not found."), + 'SyntaxError:11: [section][][sub]', + 'SyntaxError:12: [section][][sub][]'] + else: + errors = ['SyntaxError:1: [section', + 'SyntaxError:2: varval1 = value1', + 'SyntaxError:3: varval2 = value2', + ("VariableError:4: can not create namespace" + " '.section' in not 'custom' namespace."), + 'SyntaxError:6: eee', + 'SyntaxError:8: [section2', + ("VariableError:10: can not clear namespace 'section'." + " Namespace is not found."), + 'SyntaxError:11: [section][][sub]', + 'SyntaxError:12: [section][][sub][]'] namespace_filler.fill(datavars, input_text) for parsed_error, error in zip(namespace_filler.errors, errors): assert parsed_error == error + def test_if_a_variable_is_created_as_readonly_variable_and_there_is_a_try_to_new_value_using_ini_file__the_ini_parser_does_not_change_the_variable_and_sets_variable_error(self): + Namespace.reset() + datavars = Namespace.datavars + namespace_filler = NamespaceIniFiller() + + with Namespace('os'): + Variable('var_1', source='value_1', type=StringType.readonly) + Variable('var_2', type=HashType.readonly, + source={'key_1': 'value_1', 'key_2': 'value_2'}) + + first_ini_text = """[os] +var_1 = new_value + +[os][var_2] +key_1 = new_value +""" + + namespace_filler.fill(datavars, first_ini_text) + assert datavars.os.var_1 == 'value_1' + assert datavars.os.var_2.get_hash() == {'key_1': 'value_1', + 'key_2': 'value_2'} + assert namespace_filler.errors == ["VariableError:2: can not change" + " readonly variable 'os.var_1'", + "VariableError:5: can not change" + " readonly hash variable 'os.var_2'" + ] + def test_if_calculate_ini_file_is_used_for_changing_of_an_unfixed_hash_variable_created_using_the_variables_api_and_for_adding_of_the_new_key_in_it__the_NamespaceIniFiller_object_changes_hash_value_and_adds_a_new_key(self): Namespace.reset() datavars = Namespace.datavars @@ -848,7 +958,6 @@ key_3 = value_3 """ namespace_filler.fill(datavars, first_ini_text) - print('NEW HASH VALUE: {}'.format(datavars.os.var_1.get_hash())) assert datavars.os.var_1.get_hash() == {'key_1': 'new_value', 'key_2': 'value_2', 'key_3': 'value_3'} @@ -984,6 +1093,7 @@ value = weird_value """ namespace_filler.fill(datavars, first_ini_text) + assert datavars.custom.test ==\ [{'name': 'name_0', 'value': 'value_0'}] assert datavars.os.test ==\ @@ -1136,8 +1246,8 @@ value = another_value assert datavars.os.linux.subname == 'KDE' assert datavars.os.linux.arch == 'amd64' - assert datavars.os.hashvar.value1 == '20' - assert datavars.os.hashvar.value2 == '30' + assert datavars.os.hashvar.value1 == 'new1' + assert datavars.os.hashvar.value2 == 'new2' assert datavars.os.tablevar == [ {'dev': '/dev/sda1', 'mount': 'swap'}, @@ -1154,8 +1264,8 @@ value = another_value assert datavars.os.linux.subname == 'KDE' assert datavars.os.linux.arch == 'amd64' - assert datavars.os.hashvar.value1 == '20' - assert datavars.os.hashvar.value2 == '30' + assert datavars.os.hashvar.value1 == 'new1' + assert datavars.os.hashvar.value2 == 'new2' assert datavars.os.tablevar == [ {'dev': '/dev/sda1', 'mount': 'swap'}, @@ -1233,6 +1343,25 @@ value = another_value assert datavars.os.calculate == 'new1 new2' + def test_if_Datavars_object_is_created_and_all_variables_are_loaded__the_set_method_can_be_used_for_changing_hash_values_with_saving_of_their_sources(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_6')) + datavars.os.hashvar['value1'].set('old but gold') + assert datavars.os.hashvar.get_hash() == {'value1': 'old but gold', + 'value2': 'new2'} + + datavars.os['hashvar'].reset() + assert datavars.os.hashvar.get_hash() == {'value1': 'new1', + 'value2': 'new2'} + + datavars.os.hashvar['value2'].set('almost blue') + assert datavars.os.hashvar.get_hash() == {'value1': 'new1', + 'value2': 'almost blue'} + + datavars.os.hashvar['value2'].reset() + assert datavars.os.hashvar.get_hash() == {'value1': 'new1', + 'value2': 'new2'} + # Теперь тестируем применение объекта Datavars в шаблонах. def test_if_Datavars_object_is_set_as_the_datavars_for_the_TemplateEngine_object__variables_from_the_Datavars_object_can_be_inserted_in_the_processed_template(self): datavars = Datavars( @@ -1394,16 +1523,12 @@ id_2 = 1349''' read_file(datavars.system.env_path['system'].value) # Теперь тестируем обработку ошибок. - def test_process_ini_errors(self): + def test_if_calculate_ini_file_has_some_syntax_errors_in_its_text__loader_skips_incorrect_lines_and_puts_some_error_messages_in_the_output(self): io_module = IOModule(save_messages=True) - datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, - 'variables_12'), - io_module=io_module) + Datavars(variables_path=os.path.join(TESTFILES_PATH, 'variables_12'), + io_module=io_module) - messages = [('info', ("Loading variables from file: /home/divanov/Home" - "/development/calculate-lib/tests/variables/testfiles/" - "ini_vars/calculate_8.ini")), - ('error', 'SyntaxError:1: [section'), + messages = [('error', 'SyntaxError:1: [section'), ('error', 'SyntaxError:2: varval1 = value1'), ('error', 'SyntaxError:3: varval2 = value2'), ('error', ("VariableError:4: variables package 'section'" @@ -1415,13 +1540,70 @@ id_2 = 1349''' " found.")), ('error', 'SyntaxError:11: [section][][sub]'), ('error', 'SyntaxError:12: [section][][sub][]'), - ('warning', 'Some variables was not loaded.')] + ('warning', 'Some variables was not loaded.'), + ('success', "Variables from 'system' are loaded"), + ('info', "Loading variables from file: 'local'"), + ('warning', ("File 'local' is not found. Variables" + " are not loaded"))] for message, datavars_message in zip( messages, io_module.messages[-len(messages):]): assert message == datavars_message - assert False + + def test_if_any_variables_module_has_syntax_error_in_its_code__the_variables_loader_raises_the_VariableError_exception(self): + with pytest.raises(VariableError): + io_module = IOModule(save_messages=True) + Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_13'), + io_module=io_module) + + def test_if_datavars_object_is_used_to_get_access_to_an_unexisting_variable__the_datavars_module_raises_the_VariableNot_Found_exception(self): + io_module = IOModule(save_messages=True) + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_14'), + io_module=io_module) + + with pytest.raises(VariableNotFoundError): + datavars.level.variable + + def test_if_some_template_processed_by_the_template_engine_tries_to_get_value_of_a_variable_that_does_not_exist__the_template_engine_raises_the_VariableNotFound_error(self): + datavars = Datavars( + variables_path=os.path.join(TESTFILES_PATH, + 'variables_15')) + template_engine = TemplateEngine(appends_set=APPENDS_SET, + chroot_path=TESTFILES_PATH, + datavars_module=datavars) + input_template = '''{% calculate name = 'filename', force -%} +os.linux.test_3 = {{ os.linux.test_3 }} +''' + + with pytest.raises(VariableNotFoundError): + template_engine.process_template_from_string(input_template, FILE) + + def test_two_datavars_objects(self): + datavars_1 = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_7')) + datavars_2 = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_7')) + + input_template_1 = '''{% calculate name = 'filename', force -%} +{%- save os.linux.test = 'new_first' -%}''' + input_template_2 = '''{% calculate name = 'filename', force -%} +{%- save os.linux.test = 'new_second' -%}''' + + template_engine_1 = TemplateEngine(appends_set=APPENDS_SET, + chroot_path=TESTFILES_PATH, + datavars_module=datavars_1) + template_engine_2 = TemplateEngine(appends_set=APPENDS_SET, + chroot_path=TESTFILES_PATH, + datavars_module=datavars_2) + + template_engine_1.process_template_from_string(input_template_1, FILE) + template_engine_2.process_template_from_string(input_template_2, FILE) + + assert datavars_1.os.linux.test == 'new_first' + assert datavars_2.os.linux.test == 'new_second' def test_for_removing_testfiles(self): shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo')) diff --git a/tests/variables/test_parameters.py b/tests/variables/test_parameters.py new file mode 100644 index 0000000..dc67153 --- /dev/null +++ b/tests/variables/test_parameters.py @@ -0,0 +1,1090 @@ +import os +import shutil +import pytest +from collections import OrderedDict +from calculate.variables.parameters import BaseParameter, Integer, String,\ + Description, Parameters,\ + ValidationError, Choice, Bool,\ + List, CyclicValidationError,\ + ParameterError, Table, TableValue +from calculate.variables.loader import Datavars + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + + +@pytest.mark.parameters +class TestParameters: + def test_to_make_testfiles(self): + shutil.copytree(os.path.join(TESTFILES_PATH, 'gentoo.backup'), + os.path.join(TESTFILES_PATH, 'gentoo'), + symlinks=True) + + def test_if_some_parameters_are_created_using_classes_inheriting_the_BaseParameter_class_and_its_types_is_set_in_the_parameter_s_classes__the_parameters_can_be_created_using_this_classes_and_their_instances_types_are_types_from_their_classes(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class MyShinyParameter(BaseParameter): + type = Integer() + + def validate(self, var): + if var.value < 10: + raise ValidationError("The value must be greater than 10") + + def bind_method(self, var): + return var.value, None + + class AnotherParameter(BaseParameter): + type = String() + + def bind_method(self): + return 'default string', None + + def validate(self, value): + if not value.startswith('/var/lib'): + raise ValidationError("The value must starts with a" + " '/var/lib'") + + class OneMoreParameter(BaseParameter): + type = String() + + def bind_method(self): + return 'default string', None + + def validate(self, value): + available_set = {'mystery', 'horror', 'weird'} + if value not in available_set: + raise ValidationError(f"The value '{value}' is not in" + f" available. Available values:" + f" {', '.join(available_set)}") + + # Создание параметров данных типов. + PARAMS.add( + MyShinyParameter('my-shiny', 'The Shiniest ones', + Description( + short='The shiny parameter', + full='The shiniest thing known to science.', + usage='-S COUNT, --my-shiny COUNT'), + shortname='S').bind('os.linux.test_5'), + AnotherParameter('my-other', 'The Others', + Description(short='Not from this world.'), + shortname='o'), + OneMoreParameter('one-more', 'The Others', + Description(full='Plan 9 from outer space.'))) + + shiny_one, another_one, weird_one = PARAMS + + shiny_one.set(9) + assert type(shiny_one._parameter_type) == Integer + + another_one.set('/var/lib/bla') + assert type(another_one._parameter_type) == String + + weird_one.set('weird') + assert type(weird_one._parameter_type) == String + + def test_if_parameter_is_created_with_bind_method_with_a_variable_in_its_arguments__the_default_parameter_value_is_calculated_using_this_method_and_a_variable_from_arguments_can_invalidate_the_parameter(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class TestParameter(BaseParameter): + type = String() + + def bind_method(self, fullname): + return fullname.value, None + + # Создание параметров данных типов. + PARAMS.add( + TestParameter('test', 'Test', + Description( + short='Test parameter', + full='Test parameters are needed for tests.'), + shortname='t').bind('os.linux.fullname')) + + assert PARAMS['test'] == PARAMS['t'] + assert PARAMS['test'].value == 'Calculate Linux Desktop' + datavars.os.linux['fullname'].set('Gentoo Linux') + assert PARAMS['test'].value == 'Gentoo Linux' + + PARAMS['test'].set('Arch Linux') + assert PARAMS['test'].value == 'Arch Linux' + + datavars.os.linux['fullname'].set('Void Linux') + assert PARAMS['test'].value == 'Arch Linux' + + def test_if_bind_method_is_set_for_parameter_and_then_the_set_method_is_used_to_change_value__the_parameters_value_is_changed_and_a_variable_from_a_bounded_variable_is_not_able_to_invalidate_parameter_s_value(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class TestParameter(BaseParameter): + type = String() + + def bind_method(self, fullname): + return fullname.value, None + + # Создание параметров данных типов. + PARAMS.add( + TestParameter('test', 'Test', + Description( + short='Test parameter', + full='Test parameters are needed for tests.'), + shortname='t').bind('os.linux.fullname')) + + assert PARAMS['test'].value == 'Calculate Linux Desktop' + datavars.os.linux['fullname'].set('Gentoo Linux') + assert PARAMS['test'].value == 'Gentoo Linux' + + PARAMS['test'].set('Arch Linux') + assert PARAMS['test'].value == 'Arch Linux' + + datavars.os.linux['fullname'].set('Void Linux') + assert PARAMS['test'].value == 'Arch Linux' + + def test_if_the_bind_method_is_set_for_two_variables__the_bind_method_calculates_parameter_s_default_value__variables_can_invalidate_parameter_s_value_before_the_set_method_is_used__the_set_parameter_can_change_value_of_the_parameter(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class TestParameter(BaseParameter): + type = String() + + def bind_method(self, fullname, shortname): + return f'{fullname.value} -> {shortname.value}', None + + # Создание параметров данных типов. + PARAMS.add( + TestParameter('test', 'Test', + Description( + short='Test parameter', + full='Test parameters are needed for tests.'), + shortname='t').bind('os.linux.fullname', + 'os.linux.shortname') + .to_set('os.linux.subname')) + + assert PARAMS['test'].value == 'Calculate Linux Desktop -> CLD' + datavars.os.linux['fullname'].set('Gentoo Linux') + assert PARAMS['test'].value == 'Gentoo Linux -> CLD' + datavars.os.linux['shortname'].set('GL') + assert PARAMS['test'].value == 'Gentoo Linux -> GL' + + # Переменные, указанные в to_set не меняются если используется + # дефолтное значение, рассчитанное с помощью переменных. + assert datavars.os.linux.subname == 'KDE' + + PARAMS['test'].set('Arch Linux -> AL') + assert PARAMS['test'].value == 'Arch Linux -> AL' + + # Теперь меняются. + assert datavars.os.linux.subname == 'Arch Linux -> AL' + + datavars.os.linux['fullname'].set('Void Linux') + assert PARAMS['test'].value == 'Arch Linux -> AL' + + def test_if_hash_value_is_in_the_set_variables_list__the_parameter_is_able_to_change_that_hash_s_value(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class TestParameter(BaseParameter): + type = String() + + def bind_method(self, fullname): + return fullname.value, True + + # Создание параметров данных типов. + PARAMS.add( + TestParameter('test', 'Test', + Description( + short='Test parameter', + full='Test parameters are needed for tests.'), + shortname='t' + ).bind('os.linux.fullname').to_set( + 'os.linux.shortname', + 'os.hashvar.value2')) + + PARAMS['test'].set('CLD') + assert datavars.os.linux.shortname == 'CLD' + assert datavars.os.hashvar.get_hash() == {'value1': 'new1', + 'value2': 'CLD'} + + PARAMS['test'].set('CLDX') + assert datavars.os.linux.shortname == 'CLDX' + assert datavars.os.hashvar.get_hash() == {'value1': 'new1', + 'value2': 'CLDX'} + + def test_if_bind_method_is_set_for_parameter__the_method_can_return_as_second_value_of_the_return_tuple_disactivity_comment_witch_disactivates_the_parameter_even_if_parameters_values_is_set_by_user(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class BoolTestParameter(BaseParameter): + type = Bool() + + def bind_method(self): + return False, None + + class StringTestParameter(BaseParameter): + type = String() + + def bind_method(self, string, boolean): + disactivity_comment = None + if boolean.value: + disactivity_comment = "Disactivated by test bool parameter" + return string.value, disactivity_comment + + # Создание параметров данных типов. + PARAMS.add( + BoolTestParameter('test-bool', 'Booleans', + Description( + short='Test bool parameter', + full=('Test bool parameter, that can' + ' disactivate other parameter')), + shortname='b').to_set('os.linux.test_2'), + StringTestParameter('test-string', 'Strings', + Description( + short='Test string parameter', + full=('Test string parameter, that' + ' can be disactivated')), + shortname='s').bind('os.linux.test_3', + 'os.linux.test_2')) + + assert PARAMS['test-bool'].value is False + assert PARAMS['test-string'].value == 'test string' + + PARAMS['test-bool'].set(True) + assert PARAMS['test-string'].disactivity_comment ==\ + "Disactivated by test bool parameter" + + PARAMS['test-bool'].set(False) + PARAMS['test-string'].set("My own value") + assert PARAMS['test-string'].value == "My own value" + assert PARAMS['test-string'].disactivity_comment is None + + PARAMS['test-bool'].set(True) + assert PARAMS['test-string'].value == "My own value" + assert PARAMS['test-string'].disactivity_comment ==\ + "Disactivated by test bool parameter" + + def test_Choice_type_is_set_for_parameter_and_bind_method_is_set_too__the_bind_method_can_define_available_values_for_the_choice_parameter__choice_availables_is_invalidatable_even_if_the_value_is_set_by_user__choice_type_checks_if_the_new_value_is_the_availables_list(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class TestParameter(BaseParameter): + type = Choice() + + def bind_method(self, choices_list): + choices_list = choices_list.value + for choice in choices_list: + self.choices.update({choice: f'Comment for {choice}'}) + return choices_list[0], None + + # Создание параметров данных типов. + PARAMS.add( + TestParameter('test', 'Test', + Description( + short='Test parameter', + full='Test parameters are needed for tests.'), + shortname='t').bind('os.linux.test_1')) + + assert PARAMS['test'].value == 'choice_1' + PARAMS['test'].set(PARAMS['test'].check_value_type('choice_2')) + assert PARAMS['test'].value == 'choice_2' + + with pytest.raises(ValidationError): + PARAMS['test'].set(PARAMS['test'].check_value_type('unavailable')) + + def test_if_some_parameters_are_created_with_some_types_and_there_are_attempts_to_assign_some_values_which_type_is_not_correct_to_this_parameters__the_parameters_types_checks_this_values_and_raises_ValidationError_exception(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Choice() + + def bind_method(self, choices_list): + choices_list = choices_list.value + for choice in choices_list: + self.choices.update({choice: f'Comment for {choice}'}) + return choices_list[0], None + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + class ThirdTestParameter(BaseParameter): + type = List() + + def bind_method(self): + return [1, 2, 3, 4], None + + # Создание параметров данных типов. + PARAMS.add( + FirstTestParameter('test_1', 'Test', + Description( + short='Test choice', + full=('Test parameters are' + ' needed for tests.')) + ).bind('os.linux.test_1'), + SecondTestParameter('test_2', 'Test', + Description(short='Test choice', + full=('Test parameters are' + ' needed for tests.'))), + ThirdTestParameter('test_3', 'Test', + Description(short='Test list', + full=('Test parameters are' + ' needed for tests.')))) + + with pytest.raises(ValidationError): + PARAMS.set_parameters({'test_1': 'unavailable'}) + + with pytest.raises(ValidationError): + PARAMS.set_parameters({'test_2': 123}) + + with pytest.raises(ValidationError): + PARAMS.set_parameters({'test_3': 'suspicious value'}) + + def test_if_some_parameters_is_created_with_the_validate_methods__the_validate_methods_are_used_for_validating_all_values_that_is_set_to_the_parameter(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Choice() + + def bind_method(self, choices_list): + choices_list = choices_list.value + for choice in choices_list: + self.choices.update({choice: f'Comment for {choice}'}) + return choices_list[0], None + + def validate(self, parameters, datavars, value): + if datavars.os.linux.test_2 and bool(int(value[-1]) % 2): + raise ValidationError('Choice must be with even number.') + elif not datavars.os.linux.test_2 and not bool( + int(value[-1]) % 2): + raise ValidationError('Choice must be with odd number.') + print(f'VALIDATED: {self._name}') + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + def validate(self, parameters, datavars, value): + if (bool(int(parameters['test-1'].value[-1]) % 2) and + value != 'odd'): + raise ValidationError('Value must be "odd".') + elif (not bool(int(parameters['test-1'].value[-1]) % 2) and + value != 'even'): + raise ValidationError('Value must be "even".') + elif value not in {'odd', 'even'}: + raise ValidationError('Value must be "odd" or "value"') + print(f'VALIDATED: {self._name}') + + class ThirdTestParameter(BaseParameter): + type = List() + + def bind_method(self): + return ['/dev/sda1'], None + + def validate(self, parameters, datavars, value): + prefix = datavars.os.linux.test_4 + for device in value: + if not device.startswith(prefix): + raise ValidationError("Partitions must be from the" + " '{prefix}' device") + print(f'VALIDATED: {self._name}') + + # Создание параметров данных типов. + PARAMS.add( + FirstTestParameter('test-1', 'Test', + Description( + short='Test choice', + full=('Test parameters are' + ' needed for tests.')) + ).bind('os.linux.test_1'), + SecondTestParameter('test-2', 'Test', + Description(short='Test choice', + full=('Test parameters are' + ' needed for tests.'))), + ThirdTestParameter('test-3', 'Test', + Description(short='Test list', + full=('Test parameters are' + ' needed for tests.')))) + + set_values = OrderedDict() + set_values.update({'test-2': 'odd', + 'test-1': 'choice_3', + 'test-3': ['/dev/sda1', '/dev/sda2', '/dev/sda3']}) + PARAMS.set_parameters(set_values) + assert PARAMS['test-1']._value == 'choice_3' + assert PARAMS['test-2']._value == 'odd' + assert PARAMS['test-3']._value == ['/dev/sda1', + '/dev/sda2', + '/dev/sda3'] + + with pytest.raises(ValidationError): + PARAMS.set_parameters({'test-3': ['/dev/sdb1', '/dev/sdb2']}) + + with pytest.raises(ValidationError): + PARAMS.set_parameters({'test-2': 'even'}) + + with pytest.raises(ValidationError): + PARAMS.set_parameters({'test-1': 'choice_5'}) + + def test_if_some_parameters_are_created_with_validation_methods_that_use_parameters_forming_cyclic_validation_process__the_parameters_throw_the_CyclicValidationError_exception(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Choice() + + def bind_method(self, choices_list): + choices_list = choices_list.value + for choice in choices_list: + self.choices.update({choice: f'Comment for {choice}'}) + return choices_list[0], None + + def validate(self, parameters, datavars, value): + if len(parameters['test-3'].value) < 5: + raise ValidationError('Too many devices.') + if datavars.os.linux.test_2 and bool(int(value[-1]) % 2): + raise ValidationError('Choice must be with even number.') + elif not datavars.os.linux.test_2 and not bool( + int(value[-1]) % 2): + raise ValidationError('Choice must be with odd number.') + print(f'VALIDATED: {self._name}') + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + def validate(self, parameters, datavars, value): + if (bool(int(parameters['test-1'].value[-1]) % 2) and + value != 'odd'): + raise ValidationError('Value must be "odd".') + elif (not bool(int(parameters['test-1'].value[-1]) % 2) and + value != 'even'): + raise ValidationError('Value must be "even".') + elif value not in {'odd', 'even'}: + raise ValidationError('Value must be "odd" or "value"') + print(f'VALIDATED: {self._name}') + + class ThirdTestParameter(BaseParameter): + type = List() + + def bind_method(self): + return ['/dev/sda1'], None + + def validate(self, parameters, datavars, value): + if parameters['test-2'].value == 'even': + raise ValidationError("Must be odd lol") + prefix = datavars.os.linux.test_4 + for device in value: + if not device.startswith(prefix): + raise ValidationError("Partitions must be from the" + " '{prefix}' device") + print(f'VALIDATED: {self._name}') + + # Создание параметров данных типов. + PARAMS.add( + FirstTestParameter('test-1', 'Test', + Description( + short='Test choice', + full=('Test parameters are' + ' needed for tests.')) + ).bind('os.linux.test_1'), + SecondTestParameter('test-2', 'Test', + Description(short='Test choice', + full=('Test parameters are' + ' needed for tests.'))), + ThirdTestParameter('test-3', 'Test', + Description(short='Test list', + full=('Test parameters are' + ' needed for tests.')))) + assert PARAMS['test-1']._value is None + PARAMS.order = ['test-1', 'test-2', 'test-3'] + + set_values = OrderedDict() + set_values.update({'test-2': 'odd', + 'test-1': 'choice_3', + 'test-3': ['/dev/sda1', '/dev/sda2', '/dev/sda3']}) + with pytest.raises(CyclicValidationError): + PARAMS.set_parameters(set_values) + + def test_if_two_parameters_is_created_with_the_bind_methods_and_one_of_them_can_be_disactivated_depending_on_the_variable_and_other_one_can_change_this_variable_s_value__the_second_parameter_can_disactivate_the_first_one_through_variable(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Choice() + + def bind_method(self, choices_list, flag_variable): + disactivity_comment = None + if flag_variable.value: + disactivity_comment = "Disactive because of flag" + choices_list = choices_list.value + for choice in choices_list: + self.choices.update({choice: f'Comment for {choice}'}) + return choices_list[0], disactivity_comment + + class SecondTestParameter(BaseParameter): + type = Bool() + + def bind_method(self): + return True, None + + # Создание параметров данных типов. + PARAMS.add( + FirstTestParameter('test-1', 'Test', + Description( + short='Test choice', + full=('Test parameters are' + ' needed for tests.')) + ).bind('os.linux.test_1', + 'os.linux.test_2'), + SecondTestParameter('test-2', 'Test', + Description(short='Test bool', + full=('Test parameters are' + ' needed for tests.') + )).to_set('os.linux.test_2')) + + assert PARAMS['test-1'].disactivity_comment is None + PARAMS.set_parameters({'test-1': 'choice_2', 'test-2': True}) + assert PARAMS['test-1'].disactivity_comment ==\ + "Disactive because of flag" + assert PARAMS['test-1'].value == 'choice_2' + PARAMS.set_parameters({'test-2': False}) + + def test_if_there_is_an_attempt_to_add_in_the_parameters_container_new_parameter_but_the_container_already_has_parameter_with_the_same_fullname__the_parameters_container_throws_ParameterError_exception(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + # Создание параметров данных типов. + with pytest.raises(ParameterError): + PARAMS.add( + FirstTestParameter('test', 'Test', + Description( + short='Test string', + full=('Test parameters are' + ' needed for tests.'))), + SecondTestParameter('test', 'Test', + Description(short='Test string', + full=('Test parameters are' + ' needed for tests.')))) + + def test_if_there_is_an_attempt_to_add_in_the_parameters_container_new_parameter_but_the_container_already_has_parameter_with_the_same_shortname__the_parameters_container_throws_ParameterError_exception(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + # Создание параметров данных типов. + with pytest.raises(ParameterError): + PARAMS.add( + FirstTestParameter('test-1', 'Test', + Description( + short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='t'), + SecondTestParameter('test-2', 'Test', + Description(short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='t')) + + def test_if_some_parameters_are_created_with_different_argv_values__their_position_number_will_be_saved_in_the_parameters_container(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + class ThirdTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + # Создание параметров данных типов. + PARAMS.add( + FirstTestParameter('test-a', 'Test 1', + Description( + short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='a', argv=0), + SecondTestParameter('test-b', 'Test 2', + Description(short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='b', argv=3), + SecondTestParameter('test-c', 'Test 3', + Description(short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='c', argv=1)) + assert PARAMS._argvs == [PARAMS['test-a'], PARAMS['test-c'], + None, PARAMS['test-b']] + + def test_if_there_is_an_attempt_to_add_in_the_parameters_container_new_parameter_but_the_container_already_has_parameter_with_the_same_argv_value__the_parameters_container_throws_ParameterError_exception(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + class SecondTestParameter(BaseParameter): + type = String() + + def bind_method(self): + return "default string", None + + # Создание параметров данных типов. + with pytest.raises(ParameterError): + PARAMS.add( + FirstTestParameter('test-1', 'Test', + Description( + short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='t', argv=0), + SecondTestParameter('test-2', 'Test', + Description(short='Test string', + full=('Test parameters are' + ' needed for tests.')), + shortname='t', argv=0)) + + def test_if_parameter_is_created_with_the_bind_method__the_bind_method_can_get_access_to_the_current_parameter_value_during_the_value_calculation(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = String() + + def bind_method(self, test_var, test_comment): + output = "/dev/sda" + test_var_value = test_var.value + if self.value is not None: + number = int(self.value[8:]) + if test_var_value > number: + number += 1 + elif number: + number -= 1 + output = output + str(number) + else: + output = output + '0' + + self._description.full = test_comment.value + return output, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test string'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t').bind('os.linux.test_5', + 'os.linux.test_6')) + + assert PARAMS['test'].value == '/dev/sda0' + datavars.os.linux['test_5'].set(6) + assert PARAMS['test'].value == '/dev/sda1' + datavars.os.linux['test_5'].set(4) + assert PARAMS['test'].value == '/dev/sda2' + datavars.os.linux['test_5'].set(2) + assert PARAMS['test'].value == '/dev/sda1' + + assert PARAMS['test']._description.full == 'Comment' + datavars.os.linux['test_6'].set("New comment") + assert PARAMS['test']._description.full == 'New comment' + + def test_if_parameter_is_created_with_table_type_and_bind_method_is_set_for_this_parameter__the_bind_method_can_be_used_for_setting_of_the_table_fields_and_its_types(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Table() + + def bind_method(self): + table = TableValue('dev', {'dev': String(), + 'mount': String()}) + table.set_comments('Device', 'Mount point') + + mount_points = ['/', '/var/calculate', '/tmp'] + for number in range(1, 4): + table.change(f'/dev/sdb{number}', mount_points[number - 1]) + + return table, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test table'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t')) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var/calculate'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + with pytest.raises(ParameterError): + PARAMS['test'].set(['/dev/sdb2', '/var', 'onemore']) + + with pytest.raises(ParameterError): + PARAMS['test'].set(['/dev/sdb2']) + + def test_if_parameter_is_created_with_table_type_and_types_of_its_fields_is_set__this_parameter_uses_this_types_for_invalidating_of_all_fields_values(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Table() + + def bind_method(self): + mounted_choices = Choice(choices=OrderedDict( + {'mounted': 'Mounted', + 'umounted': 'Not mounted', + 'auto': 'Autodetection'})) + table = TableValue('id', {'id': Integer(), + 'dev': String(), + 'mount': String(), + 'status': mounted_choices}) + table.set_comments('ID', + 'Device', + 'Mount point', + 'Status') + + mount_points = ['/', '/var/calculate', '/tmp'] + states = ['mounted', 'umounted', 'auto'] + for number in range(1, 4): + table.change(number, + f'/dev/sdb{number}', + mount_points[number - 1], + states[number - 1]) + + return table, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test table'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t')) + + assert PARAMS['test'].value.get_for_var() ==\ + [{'id': 1, 'dev': '/dev/sdb1', + 'mount': '/', 'status': 'mounted'}, + {'id': 2, 'dev': '/dev/sdb2', + 'mount': '/var/calculate', 'status': 'umounted'}, + {'id': 3, 'dev': '/dev/sdb3', + 'mount': '/tmp', 'status': 'auto'}] + + with pytest.raises(ValidationError): + PARAMS['test'].set(['woops', '/dev/sdb4', '/home', 'mounted']) + + with pytest.raises(ValidationError): + PARAMS['test'].set([3, 4, '/home', 'mounted']) + + with pytest.raises(ValidationError): + PARAMS['test'].set([3, '/dev/sdb4', False, 'mounted']) + + with pytest.raises(ValidationError): + PARAMS['test'].set([3, '/dev/sdb4', '/home', 'custom']) + + def test_if_parameter_is_created_with_table_type_and_expandable_flag_is_True__new_rows_can_be_added_to_the_created_table_and_existing_rows_can_be_modified(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Table(expandable=True) + + def bind_method(self): + table = TableValue('dev', {'dev': String(), + 'mount': String()}) + table.set_comments('Device', 'Mount point') + + mount_points = ['/', '/var/calculate', '/tmp'] + for number in range(1, 4): + table.change(f'/dev/sdb{number}', mount_points[number - 1]) + + return table, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test table'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t')) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var/calculate'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + PARAMS['test'].set(['/dev/sdb2', '/var']) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + PARAMS['test'].set(['/dev/sdb4', '/home']) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}, + {'dev': '/dev/sdb4', 'mount': '/home'}] + + def test_if_parameter_is_created_with_table_type_and_expandable_flag_is_False__existing_rows_can_be_modified_but_attempt_to_add_new_will_call_set_error_method_that_can_be_set_for_table(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Table(expandable=False) + + def bind_method(self): + table = TableValue('dev', {'dev': String(), + 'mount': String()}) + table.set_comments('Device', 'Mount point') + + mount_points = ['/', '/var/calculate', '/tmp'] + for number in range(1, 4): + table.change(f'/dev/sdb{number}', mount_points[number - 1]) + + return table, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test table'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t')) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var/calculate'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + PARAMS['test'].set(['/dev/sdb2', '/var']) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + with pytest.raises(ParameterError, + match="Device '/dev/sdb4' is not found."): + PARAMS['test'].set(['/dev/sdb4', '/home']) + + def set_error(value: str, field: str, available: list): + raise ParameterError( + f"No such {field.lower()} '{value}' in table. " + f"Available: {', '.join(available)}.") + PARAMS['test'].value.set_error = set_error + + with pytest.raises(ParameterError, + match="No such device '/dev/sdb4' in table. " + "Available: /dev/sdb[123], /dev/sdb[123], " + "/dev/sdb[123]."): + PARAMS['test'].set(['/dev/sdb4', '/home']) + + def test_if_parameter_is_created_with_table_type_and_to_set_method_is_used_to_add_variables_to_set_value__the_table_parameter_will_set_its_value_to_the_variable_from_set_list(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Table(expandable=True) + + def bind_method(self): + table = TableValue('dev', {'dev': String(), + 'mount': String()}) + table.set_comments('Device', 'Mount point') + + mount_points = ['/', '/var/calculate', '/tmp'] + for number in range(1, 4): + table.change(f'/dev/sdb{number}', mount_points[number - 1]) + + return table, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test table'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t').to_set('os.dev_table')) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var/calculate'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + PARAMS['test'].set(['/dev/sdb2', '/var']) + assert datavars.os.dev_table == [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}] + + PARAMS['test'].set(['/dev/sdb4', '/home']) + assert datavars.os.dev_table == [{'dev': '/dev/sdb1', 'mount': '/'}, + {'dev': '/dev/sdb2', 'mount': '/var'}, + {'dev': '/dev/sdb3', 'mount': '/tmp'}, + {'dev': '/dev/sdb4', 'mount': '/home'} + ] + + def test_if_parameter_is_created_with_table_type_and_fill_method_is_set__the_parameter_will_use_fill_method_to_fill_empty_fields_of_the_table(self): + datavars = Datavars(variables_path=os.path.join(TESTFILES_PATH, + 'variables_17')) + PARAMS = Parameters().initialize(datavars) + + # Описание классов параметров. + class FirstTestParameter(BaseParameter): + type = Table(expandable=True) + + def bind_method(self): + mounted_choices = Choice(choices=OrderedDict( + {'mounted': 'Mounted', + 'umounted': 'Not mounted', + 'auto': 'Autodetection'})) + table = TableValue('dev', {'dev': String(), + 'mount': String(), + 'name': String(), + 'status': mounted_choices}) + table.set_comments('Device', 'Mount point', 'Name', 'Status') + + def set_error(value: str, field: str, available: list): + raise ParameterError( + f"No such {field.lower()} '{value}' in table. " + f"Available: {', '.join(available)}.") + table.set_error = set_error + + def fill(values): + for key, row in values.items(): + for field, value in row.items(): + if value is None: + if field == 'name': + value = f'Device {int(row["dev"][8:])}' + elif field == 'status': + value = 'auto' + row[field] = value + return values + table.fill = fill + + mount_points = ['/', '/var/calculate', '/tmp'] + states = ['mounted', 'umounted', 'auto'] + names = ['Device 1', None, 'Device 3'] + for number in range(1, 4): + table.change(f'/dev/sdb{number}', + mount_points[number - 1], + names[number - 1], + states[number - 1]) + + return table, None + + # Создание параметров данных типов. + PARAMS.add(FirstTestParameter('test', 'Test', + Description(short=('Test table'), + full=('Test parameters are' + ' needed for tests.')), + shortname='t').to_set('os.dev_table')) + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/', + 'name': 'Device 1', 'status': 'mounted'}, + {'dev': '/dev/sdb2', 'mount': '/var/calculate', + 'name': 'Device 2', 'status': 'umounted'}, + {'dev': '/dev/sdb3', 'mount': '/tmp', + 'name': 'Device 3', 'status': 'auto'}] + + PARAMS['test'].set(['/dev/sdb4', '/home', None, None]) + + assert PARAMS['test'].value.get_for_var() ==\ + [{'dev': '/dev/sdb1', 'mount': '/', + 'name': 'Device 1', 'status': 'mounted'}, + {'dev': '/dev/sdb2', 'mount': '/var/calculate', + 'name': 'Device 2', 'status': 'umounted'}, + {'dev': '/dev/sdb3', 'mount': '/tmp', + 'name': 'Device 3', 'status': 'auto'}, + {'dev': '/dev/sdb4', 'mount': '/home', + 'name': 'Device 4', 'status': 'auto'}] + + def test_gui_wrapper(self): + pass + + def test_for_removing_testfiles(self): + shutil.rmtree(os.path.join(TESTFILES_PATH, 'gentoo')) diff --git a/tests/variables/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini b/tests/variables/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini index ec50aa7..7e78d71 100644 --- a/tests/variables/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini +++ b/tests/variables/testfiles/gentoo.backup/repos/distros/profiles/CLD/calculate.ini @@ -5,8 +5,8 @@ fullname = Calculate Linux Desktop subname = KDE [os][hashvar] -value1 = 20 -value2 = 30 +value1 = new1 +value2 = new2 [os][tablevar][0] dev = /dev/sda1 diff --git a/tests/variables/testfiles/parameters_ini/calculate.ini b/tests/variables/testfiles/parameters_ini/calculate.ini new file mode 100644 index 0000000..e69de29 diff --git a/tests/variables/testfiles/variables_1/level/level_2/__init__.py b/tests/variables/testfiles/variables_1/level/level_2/__init__.py index eb21a43..5d066ae 100644 --- a/tests/variables/testfiles/variables_1/level/level_2/__init__.py +++ b/tests/variables/testfiles/variables_1/level/level_2/__init__.py @@ -1,7 +1,6 @@ -from calculate.variables.datavars import Variable, StringType, Dependence +from calculate.variables.datavars import Variable, StringType, Calculate Variable('vargetter', type=StringType, - source=Dependence('main.chroot', - depend=lambda chroot: - '{} test'.format(chroot.value))) + source=Calculate(lambda chroot: '{} test'.format(chroot.value), + 'main.chroot')) diff --git a/tests/variables/testfiles/variables_13/main/__init__.py b/tests/variables/testfiles/variables_13/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/variables/testfiles/variables_13/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/variables/testfiles/variables_13/os/__init__.py b/tests/variables/testfiles/variables_13/os/__init__.py new file mode 100644 index 0000000..65d101c --- /dev/null +++ b/tests/variables/testfiles/variables_13/os/__init__.py @@ -0,0 +1,50 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType,\ + ListType, IntegerType, FloatType + +with Namespace('linux'): + Variable('shortname', source='', type=StringType) + + Variable('ver', source='', type=StringType) + + Variable('fullname', source='', type=StringType) + + Variable('subname', source='', type=StringType) + + Variable('arch', source='', type=StringType) + + Variable('test_1', source=12, type=IntegerType) + + Variable('test_2', source=1.2, type=FloatType) + + def get_title(subname, fullname, ver): + if subname.value: + return '{} {} {}'.format(fullname.value, subname.value, ver.value) + else: + return '{} {}'.format(fullname.value, ver.value) + Variable('title', type=StringType, + source=Dependence('.subname', '.fullname', '.ver', + depend=get_title)) + +Variable('hashvar', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_0', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_1', source={'key1': 'value1', + 'key2': 'value2'}, type=HashType) + +Variable('hashvar_2', source={'id_1': 1349, + 'id_2': 1575}, type=HashType) + +Variable('calculate', type=StringType, + source=Dependence('.hashvar_0', + depend=lambda hashvar: "{} {}".format( + hashvar.value['value1'], + hashvar.value['value2']))) + +Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) diff --git a/tests/variables/testfiles/variables_13/os/gentoo/__init__.py b/tests/variables/testfiles/variables_13/os/gentoo/__init__.py new file mode 100644 index 0000000..3eda2d4 --- /dev/null +++ b/tests/variables/testfiles/variables_13/os/gentoo/__init__.py @@ -0,0 +1,57 @@ +import os +from calculate.variables.datavars import Variable, Namespace, Dependence,\ + StringType, TableType +''' +gentoo: + make_profile -> string + profile: + path -> string + name -> string + repositories[*]{name, path} -> table + config -> undefined +''' + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + +# Путь до файла, указывающего на активный профиль +Variable('make_profile', type=StringType, source='/etc/portage/make.profile') + +# Параметры текущего профиля. +with Namespace('profile') + # Абсолютный путь до профиля + Variable('path', type=StringType, + source=os.path.join(TESTFILES_PATH, + "gentoo/repos/distros/profiles/CLD/amd64")) + + def get_profile_name(path, repositories): + profile_path = path.value + if not profile_path: + return "" + + for repository in repositories.value: + repository_path = repository['path'] + repository_name = repository['name'] + remove_part = os.path.normpath(os.path.join(repository_path, + "profiles")) + if profile_path.startswith(remove_part): + return "{}:{}".format(repository_name, + profile_path[len(remove_part) + 1:]) + return profile_path + # Название профиля + Variable('name', type=StringType, + source=Dependence('.path', '..repositories', + depend=get_profile_name)) + +# Информация о репозиториях +# name: имя репозитория +# path: полный путь до репозитория +Variable('repositories', type=TableType, + source=[{'name': 'distros', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/distros")}, + {'name': 'calculate', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/calculate")}, + {'name': 'gentoo', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/portage")}]) diff --git a/tests/variables/testfiles/variables_13/system/__init__.py b/tests/variables/testfiles/variables_13/system/__init__.py new file mode 100644 index 0000000..73a060d --- /dev/null +++ b/tests/variables/testfiles/variables_13/system/__init__.py @@ -0,0 +1,19 @@ +import os +from calculate.variables.datavars import Variable, ListType, HashType +''' +system: + env_order -> list + env_path -> hash +''' + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + + +# Список мест, где есть calculate.ini файлы. +Variable('env_order', type=ListType, source=['system', 'local']) + +# Отображение множества мест, где есть calculate.ini файлы, на пути к ним. +Variable('env_path', type=HashType, + source={'system': os.path.join(TESTFILES_PATH, + 'ini_vars/calculate_7.ini')}) diff --git a/tests/variables/testfiles/variables_14/level/__init__.py b/tests/variables/testfiles/variables_14/level/__init__.py new file mode 100644 index 0000000..37d9d80 --- /dev/null +++ b/tests/variables/testfiles/variables_14/level/__init__.py @@ -0,0 +1,62 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType,\ + ListType, FloatType + + +Variable('simple', type=StringType, source='simple value') + +Variable('use_local_simple', type=StringType, + source=Dependence('.simple', + depend=lambda simple: 'Using {}'.format( + simple.value))) + +Variable('use_full_simple', type=StringType, + source=Dependence('level.simple', + depend=lambda simple: 'Using {}'.format( + simple.value))) + +Variable('disks', type=ListType, + source=["/dev/sda1", "/dev/sda2", "/dev/sda3"]) + +Variable('version', type=FloatType, source='1.0') + +Variable('my_shortname', type=StringType, source='CLD') + +Variable('linux', type=HashType, + source=Dependence('.version', '.my_shortname', + depend=lambda version, my_shortname: + {'version': version.value, + 'shortname': my_shortname.value})) + +Variable('shortname_test', type=StringType, + source=Dependence('.linux.shortname', + depend=lambda shortname: '{} test'.format( + shortname.value))) + +Variable('device_list', type=ListType, + source=["/dev/sda", "/dev/sdb"]) + + +def get_device_table(device_list): + map_data = {'/dev/sda': ["hdd", "Samsung SSD"], + '/dev/sdb': ["flash", "Transcend 64GB"], + '/dev/sdc': ["usbhdd", "WD 1TB"]} + default_value = ["hdd", "Unknown"] + print('device_list = {}'.format(device_list.value)) + return [{"dev": device, + "type": map_data.get(device, default_value)[0], + "name": map_data.get(device, default_value)[1]} + for device in device_list.value] + + +Variable('device', type=TableType, source=Dependence('.device_list', + depend=get_device_table)) + +Variable('device_child', type=StringType, + source=Dependence('.device', + depend=lambda device: device.value[0]['type'])) + +with Namespace('level_3'): + Variable('my_var_1', type=StringType, source='testing') + + Variable('my_var_2', type=StringType, source='testing_2') diff --git a/tests/variables/testfiles/variables_14/level/level_2/__init__.py b/tests/variables/testfiles/variables_14/level/level_2/__init__.py new file mode 100644 index 0000000..eb21a43 --- /dev/null +++ b/tests/variables/testfiles/variables_14/level/level_2/__init__.py @@ -0,0 +1,7 @@ +from calculate.variables.datavars import Variable, StringType, Dependence + + +Variable('vargetter', type=StringType, + source=Dependence('main.chroot', + depend=lambda chroot: + '{} test'.format(chroot.value))) diff --git a/tests/variables/testfiles/variables_14/main/__init__.py b/tests/variables/testfiles/variables_14/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/variables/testfiles/variables_14/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/variables/testfiles/variables_14/os/__init__.py b/tests/variables/testfiles/variables_14/os/__init__.py new file mode 100644 index 0000000..a7ac72a --- /dev/null +++ b/tests/variables/testfiles/variables_14/os/__init__.py @@ -0,0 +1,38 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType + +with Namespace('linux'): + Variable('shortname', source='', type=StringType) + + Variable('ver', source='', type=StringType) + + Variable('fullname', source='', type=StringType) + + Variable('subname', source='', type=StringType) + + Variable('arch', source='', type=StringType) + + Variable('test', source='', type=StringType) + + def get_title(subname, fullname, ver): + if subname.value: + return '{} {} {}'.format(fullname.value, subname.value, ver.value) + else: + return '{} {}'.format(fullname.value, ver.value) + Variable('title', type=StringType, + source=Dependence('.subname', '.fullname', '.ver', + depend=get_title)) + +Variable('hashvar', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('calculate', type=StringType, + source=Dependence('.hashvar', + depend=lambda hashvar: "{} {}".format( + hashvar.value['value1'], + hashvar.value['value2']))) + +Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) diff --git a/tests/variables/testfiles/variables_15/main/__init__.py b/tests/variables/testfiles/variables_15/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/variables/testfiles/variables_15/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/variables/testfiles/variables_15/os/__init__.py b/tests/variables/testfiles/variables_15/os/__init__.py new file mode 100644 index 0000000..65d101c --- /dev/null +++ b/tests/variables/testfiles/variables_15/os/__init__.py @@ -0,0 +1,50 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType,\ + ListType, IntegerType, FloatType + +with Namespace('linux'): + Variable('shortname', source='', type=StringType) + + Variable('ver', source='', type=StringType) + + Variable('fullname', source='', type=StringType) + + Variable('subname', source='', type=StringType) + + Variable('arch', source='', type=StringType) + + Variable('test_1', source=12, type=IntegerType) + + Variable('test_2', source=1.2, type=FloatType) + + def get_title(subname, fullname, ver): + if subname.value: + return '{} {} {}'.format(fullname.value, subname.value, ver.value) + else: + return '{} {}'.format(fullname.value, ver.value) + Variable('title', type=StringType, + source=Dependence('.subname', '.fullname', '.ver', + depend=get_title)) + +Variable('hashvar', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_0', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_1', source={'key1': 'value1', + 'key2': 'value2'}, type=HashType) + +Variable('hashvar_2', source={'id_1': 1349, + 'id_2': 1575}, type=HashType) + +Variable('calculate', type=StringType, + source=Dependence('.hashvar_0', + depend=lambda hashvar: "{} {}".format( + hashvar.value['value1'], + hashvar.value['value2']))) + +Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) diff --git a/tests/variables/testfiles/variables_15/os/gentoo/__init__.py b/tests/variables/testfiles/variables_15/os/gentoo/__init__.py new file mode 100644 index 0000000..d704c66 --- /dev/null +++ b/tests/variables/testfiles/variables_15/os/gentoo/__init__.py @@ -0,0 +1,57 @@ +import os +from calculate.variables.datavars import Variable, Namespace, Dependence,\ + StringType, TableType +''' +gentoo: + make_profile -> string + profile: + path -> string + name -> string + repositories[*]{name, path} -> table + config -> undefined +''' + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + +# Путь до файла, указывающего на активный профиль +Variable('make_profile', type=StringType, source='/etc/portage/make.profile') + +# Параметры текущего профиля. +with Namespace('profile'): + # Абсолютный путь до профиля + Variable('path', type=StringType, + source=os.path.join(TESTFILES_PATH, + "gentoo/repos/distros/profiles/CLD/amd64")) + + def get_profile_name(path, repositories): + profile_path = path.value + if not profile_path: + return "" + + for repository in repositories.value: + repository_path = repository['path'] + repository_name = repository['name'] + remove_part = os.path.normpath(os.path.join(repository_path, + "profiles")) + if profile_path.startswith(remove_part): + return "{}:{}".format(repository_name, + profile_path[len(remove_part) + 1:]) + return profile_path + # Название профиля + Variable('name', type=StringType, + source=Dependence('.path', '..repositories', + depend=get_profile_name)) + +# Информация о репозиториях +# name: имя репозитория +# path: полный путь до репозитория +Variable('repositories', type=TableType, + source=[{'name': 'distros', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/distros")}, + {'name': 'calculate', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/calculate")}, + {'name': 'gentoo', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/portage")}]) diff --git a/tests/variables/testfiles/variables_15/system/__init__.py b/tests/variables/testfiles/variables_15/system/__init__.py new file mode 100644 index 0000000..73a060d --- /dev/null +++ b/tests/variables/testfiles/variables_15/system/__init__.py @@ -0,0 +1,19 @@ +import os +from calculate.variables.datavars import Variable, ListType, HashType +''' +system: + env_order -> list + env_path -> hash +''' + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + + +# Список мест, где есть calculate.ini файлы. +Variable('env_order', type=ListType, source=['system', 'local']) + +# Отображение множества мест, где есть calculate.ini файлы, на пути к ним. +Variable('env_path', type=HashType, + source={'system': os.path.join(TESTFILES_PATH, + 'ini_vars/calculate_7.ini')}) diff --git a/tests/variables/testfiles/variables_16/main/__init__.py b/tests/variables/testfiles/variables_16/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/variables/testfiles/variables_16/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/variables/testfiles/variables_16/os/__init__.py b/tests/variables/testfiles/variables_16/os/__init__.py new file mode 100644 index 0000000..65d101c --- /dev/null +++ b/tests/variables/testfiles/variables_16/os/__init__.py @@ -0,0 +1,50 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType,\ + ListType, IntegerType, FloatType + +with Namespace('linux'): + Variable('shortname', source='', type=StringType) + + Variable('ver', source='', type=StringType) + + Variable('fullname', source='', type=StringType) + + Variable('subname', source='', type=StringType) + + Variable('arch', source='', type=StringType) + + Variable('test_1', source=12, type=IntegerType) + + Variable('test_2', source=1.2, type=FloatType) + + def get_title(subname, fullname, ver): + if subname.value: + return '{} {} {}'.format(fullname.value, subname.value, ver.value) + else: + return '{} {}'.format(fullname.value, ver.value) + Variable('title', type=StringType, + source=Dependence('.subname', '.fullname', '.ver', + depend=get_title)) + +Variable('hashvar', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_0', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_1', source={'key1': 'value1', + 'key2': 'value2'}, type=HashType) + +Variable('hashvar_2', source={'id_1': 1349, + 'id_2': 1575}, type=HashType) + +Variable('calculate', type=StringType, + source=Dependence('.hashvar_0', + depend=lambda hashvar: "{} {}".format( + hashvar.value['value1'], + hashvar.value['value2']))) + +Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) diff --git a/tests/variables/testfiles/variables_16/os/gentoo/__init__.py b/tests/variables/testfiles/variables_16/os/gentoo/__init__.py new file mode 100644 index 0000000..d704c66 --- /dev/null +++ b/tests/variables/testfiles/variables_16/os/gentoo/__init__.py @@ -0,0 +1,57 @@ +import os +from calculate.variables.datavars import Variable, Namespace, Dependence,\ + StringType, TableType +''' +gentoo: + make_profile -> string + profile: + path -> string + name -> string + repositories[*]{name, path} -> table + config -> undefined +''' + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + +# Путь до файла, указывающего на активный профиль +Variable('make_profile', type=StringType, source='/etc/portage/make.profile') + +# Параметры текущего профиля. +with Namespace('profile'): + # Абсолютный путь до профиля + Variable('path', type=StringType, + source=os.path.join(TESTFILES_PATH, + "gentoo/repos/distros/profiles/CLD/amd64")) + + def get_profile_name(path, repositories): + profile_path = path.value + if not profile_path: + return "" + + for repository in repositories.value: + repository_path = repository['path'] + repository_name = repository['name'] + remove_part = os.path.normpath(os.path.join(repository_path, + "profiles")) + if profile_path.startswith(remove_part): + return "{}:{}".format(repository_name, + profile_path[len(remove_part) + 1:]) + return profile_path + # Название профиля + Variable('name', type=StringType, + source=Dependence('.path', '..repositories', + depend=get_profile_name)) + +# Информация о репозиториях +# name: имя репозитория +# path: полный путь до репозитория +Variable('repositories', type=TableType, + source=[{'name': 'distros', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/distros")}, + {'name': 'calculate', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/calculate")}, + {'name': 'gentoo', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/portage")}]) diff --git a/tests/variables/testfiles/variables_16/system/__init__.py b/tests/variables/testfiles/variables_16/system/__init__.py new file mode 100644 index 0000000..4b8bcbb --- /dev/null +++ b/tests/variables/testfiles/variables_16/system/__init__.py @@ -0,0 +1,19 @@ +import os +from calculate.variables.datavars import Variable, ListType, HashType +''' +system: + env_order -> list + env_path -> hash +''' + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + + +# Список мест, где есть calculate.ini файлы. +Variable('env_order', type=ListType, source=['system']) + +# Отображение множества мест, где есть calculate.ini файлы, на пути к ним. +Variable('env_path', type=HashType, + source={'system': os.path.join(TESTFILES_PATH, + 'parameters_ini/calculate.ini')}) diff --git a/tests/variables/testfiles/variables_17/main/__init__.py b/tests/variables/testfiles/variables_17/main/__init__.py new file mode 100644 index 0000000..43d6a38 --- /dev/null +++ b/tests/variables/testfiles/variables_17/main/__init__.py @@ -0,0 +1,4 @@ +from calculate.variables.datavars import Variable, StringType + + +Variable('chroot', type=StringType.readonly, source='/') diff --git a/tests/variables/testfiles/variables_17/os/__init__.py b/tests/variables/testfiles/variables_17/os/__init__.py new file mode 100644 index 0000000..3b40bcb --- /dev/null +++ b/tests/variables/testfiles/variables_17/os/__init__.py @@ -0,0 +1,66 @@ +from calculate.variables.datavars import Namespace, Variable, Dependence,\ + StringType, HashType, TableType,\ + BooleanType, ListType, IntegerType + +with Namespace('linux'): + Variable('shortname', source='', type=StringType) + + Variable('ver', source='', type=StringType) + + Variable('fullname', source='', type=StringType) + + Variable('subname', source='', type=StringType) + + Variable('arch', source='', type=StringType) + + Variable('test_1', source=['choice_1', + 'choice_2', + 'choice_3', + 'choice_4'], type=ListType) + + Variable('test_2', source=False, type=BooleanType) + + Variable('test_3', source='test string', type=StringType) + + Variable('test_4', source='/dev/sda', type=StringType) + + Variable('test_5', source=8, type=IntegerType) + + Variable('test_6', source="Comment", type=StringType) + + def get_title(subname, fullname, ver): + if subname.value: + return '{} {} {}'.format(fullname.value, subname.value, ver.value) + else: + return '{} {}'.format(fullname.value, ver.value) + Variable('title', type=StringType, + source=Dependence('.subname', '.fullname', '.ver', + depend=get_title)) + +Variable('hashvar', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_0', source={'value1': 'test1', + 'value2': 'test2'}, type=HashType) + +Variable('hashvar_1', source={'key1': 'value1', + 'key2': 'value2'}, type=HashType) + +Variable('hashvar_2', source={'id_1': 1349, + 'id_2': 1575}, type=HashType) + +Variable('calculate', type=StringType, + source=Dependence('.hashvar_0', + depend=lambda hashvar: "{} {}".format( + hashvar.value['value1'], + hashvar.value['value2']))) + +Variable('tablevar', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) + +Variable('dev_table', type=TableType, source=[{"dev": "/dev/sdb1", + "mount": "/"}, + {"dev": "/dev/sdb2", + "mount": "/var/calculate"}]) diff --git a/tests/variables/testfiles/variables_17/os/gentoo/__init__.py b/tests/variables/testfiles/variables_17/os/gentoo/__init__.py new file mode 100644 index 0000000..d704c66 --- /dev/null +++ b/tests/variables/testfiles/variables_17/os/gentoo/__init__.py @@ -0,0 +1,57 @@ +import os +from calculate.variables.datavars import Variable, Namespace, Dependence,\ + StringType, TableType +''' +gentoo: + make_profile -> string + profile: + path -> string + name -> string + repositories[*]{name, path} -> table + config -> undefined +''' + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + +# Путь до файла, указывающего на активный профиль +Variable('make_profile', type=StringType, source='/etc/portage/make.profile') + +# Параметры текущего профиля. +with Namespace('profile'): + # Абсолютный путь до профиля + Variable('path', type=StringType, + source=os.path.join(TESTFILES_PATH, + "gentoo/repos/distros/profiles/CLD/amd64")) + + def get_profile_name(path, repositories): + profile_path = path.value + if not profile_path: + return "" + + for repository in repositories.value: + repository_path = repository['path'] + repository_name = repository['name'] + remove_part = os.path.normpath(os.path.join(repository_path, + "profiles")) + if profile_path.startswith(remove_part): + return "{}:{}".format(repository_name, + profile_path[len(remove_part) + 1:]) + return profile_path + # Название профиля + Variable('name', type=StringType, + source=Dependence('.path', '..repositories', + depend=get_profile_name)) + +# Информация о репозиториях +# name: имя репозитория +# path: полный путь до репозитория +Variable('repositories', type=TableType, + source=[{'name': 'distros', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/distros")}, + {'name': 'calculate', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/repos/calculate")}, + {'name': 'gentoo', + 'path': os.path.join(TESTFILES_PATH, + "gentoo/portage")}]) diff --git a/tests/variables/testfiles/variables_17/system/__init__.py b/tests/variables/testfiles/variables_17/system/__init__.py new file mode 100644 index 0000000..4b8bcbb --- /dev/null +++ b/tests/variables/testfiles/variables_17/system/__init__.py @@ -0,0 +1,19 @@ +import os +from calculate.variables.datavars import Variable, ListType, HashType +''' +system: + env_order -> list + env_path -> hash +''' + + +TESTFILES_PATH = os.path.join(os.getcwd(), 'tests/variables/testfiles') + + +# Список мест, где есть calculate.ini файлы. +Variable('env_order', type=ListType, source=['system']) + +# Отображение множества мест, где есть calculate.ini файлы, на пути к ним. +Variable('env_path', type=HashType, + source={'system': os.path.join(TESTFILES_PATH, + 'parameters_ini/calculate.ini')})