diff --git a/wlauto/commands/run.py b/wlauto/commands/run.py index 9eb43f92..12038b1e 100644 --- a/wlauto/commands/run.py +++ b/wlauto/commands/run.py @@ -25,6 +25,7 @@ from wlauto.core.configuration import RunConfiguration from wlauto.core.configuration.parsers import AgendaParser, ConfigParser from wlauto.core.execution import Executor from wlauto.core.output import init_wa_output +from wlauto.core.version import get_wa_version from wlauto.exceptions import NotFoundError, ConfigError from wlauto.utils.log import add_log_file from wlauto.utils.types import toggle_set @@ -74,23 +75,26 @@ class RunCommand(Command): This option may be specified multiple times. """) - def execute(self, state, args): - output = self.set_up_output_directory(state, args) + def execute(self, config, args): + output = self.set_up_output_directory(config, args) add_log_file(output.logfile) + self.logger.debug('Version: {}'.format(get_wa_version())) + self.logger.debug('Command Line: {}'.format(' '.join(sys.argv))) + disabled_instruments = toggle_set(["~{}".format(i) for i in args.instruments_to_disable]) - state.jobs_config.disable_instruments(disabled_instruments) - state.jobs_config.only_run_ids(args.only_run_ids) + config.jobs_config.disable_instruments(disabled_instruments) + config.jobs_config.only_run_ids(args.only_run_ids) parser = AgendaParser() if os.path.isfile(args.agenda): - parser.load_from_path(state, args.agenda) + parser.load_from_path(config, args.agenda) else: try: pluginloader.get_plugin_class(args.agenda, kind='workload') agenda = {'workloads': [{'name': args.agenda}]} - parser.load(state, agenda, 'CMDLINE_ARGS') + parser.load(config, agenda, 'CMDLINE_ARGS') except NotFoundError: msg = 'Agenda file "{}" does not exist, and there no workload '\ 'with that name.\nYou can get a list of available '\ @@ -98,16 +102,16 @@ class RunCommand(Command): raise ConfigError(msg.format(args.agenda)) executor = Executor() - executor.execute(state, output) + executor.execute(config, output) - def set_up_output_directory(self, state, args): + def set_up_output_directory(self, config, args): if args.output_directory: output_directory = args.output_directory else: output_directory = settings.default_output_directory self.logger.debug('Using output directory: {}'.format(output_directory)) try: - return init_wa_output(output_directory, state, args.force) + return init_wa_output(output_directory, config, args.force) except RuntimeError as e: if 'path exists' in str(e): msg = 'Output directory "{}" exists.\nPlease specify another '\ diff --git a/wlauto/core/command.py b/wlauto/core/command.py index 4cbf424e..4b2a7d93 100644 --- a/wlauto/core/command.py +++ b/wlauto/core/command.py @@ -70,7 +70,7 @@ class Command(Plugin): """ Execute this command. - :state: An initialized ``WAState`` that contains the current state of + :state: An initialized ``ConfigManager`` that contains the current state of WA exeuction up to that point (processed configuraition, loaded plugins, etc). :args: An ``argparse.Namespace`` containing command line arguments (as returned by diff --git a/wlauto/core/configuration/configuration.py b/wlauto/core/configuration/configuration.py index 3e7ec323..0fb6b5cf 100644 --- a/wlauto/core/configuration/configuration.py +++ b/wlauto/core/configuration/configuration.py @@ -17,7 +17,7 @@ import re from copy import copy from collections import OrderedDict, defaultdict -from wlauto.exceptions import ConfigError +from wlauto.exceptions import ConfigError, NotFoundError from wlauto.utils.misc import (get_article, merge_config_values) from wlauto.utils.types import (identifier, integer, boolean, list_of_strings, toggle_set, @@ -489,6 +489,15 @@ class CpuFreqParameters(object): ##################### +def _to_pod(cfg_point, value): + if is_pod(value): + return value + if hasattr(cfg_point.kind, 'to_pod'): + return value.to_pod() + msg = '{} value "{}" is not serializable' + raise ValueError(msg.format(cfg_point.name, value)) + + class Configuration(object): config_points = [] @@ -498,16 +507,17 @@ class Configuration(object): configuration = {cp.name: cp for cp in config_points} @classmethod - # pylint: disable=unused-argument - def from_pod(cls, pod, plugin_cache): + def from_pod(cls, pod): instance = cls() - for name, cfg_point in cls.configuration.iteritems(): + for cfg_point in cls.config_points: if name in pod: - cfg_point.set_value(instance, pod.pop(name)) + value = pod.pop(name) + if hasattr(cfg_point.kind, 'from_pod'): + value = cfg_point.kind.from_pod(value) + cfg_point.set_value(instance, value) if pod: msg = 'Invalid entry(ies) for "{}": "{}"' - raise ConfigError(msg.format(cls.name, '", "'.join(pod.keys()))) - instance.validate() + raise ValueError(msg.format(cls.name, '", "'.join(pod.keys()))) return instance def __init__(self): @@ -531,17 +541,17 @@ class Configuration(object): def to_pod(self): pod = {} - for cfg_point_name in self.configuration.iterkeys(): - value = getattr(self, cfg_point_name, None) + for cfg_point in self.configuration.itervalues(): + value = getattr(self, cfg_point.name, None) if value is not None: - pod[cfg_point_name] = value + pod[cfg_point.name] = _to_pod(cfg_point, value) return pod # This configuration for the core WA framework -class WAConfiguration(Configuration): +class MetaConfiguration(Configuration): - name = "WA Configuration" + name = "Meta Configuration" plugin_packages = [ 'wlauto.commands', @@ -617,7 +627,7 @@ class WAConfiguration(Configuration): return os.path.join(self.user_directory, 'config.yaml') def __init__(self, environ): - super(WAConfiguration, self).__init__() + super(MetaConfiguration, self).__init__() user_directory = environ.pop('WA_USER_DIRECTORY', '') if user_directory: self.set('user_directory', user_directory) @@ -748,39 +758,30 @@ class RunConfiguration(Configuration): selected device. """ # pylint: disable=no-member - self.device_config = plugin_cache.get_plugin_config(self.device_config, + if self.device is None: + msg = 'Attemting to merge device config with unspecified device' + raise RuntimeError(msg) + self.device_config = plugin_cache.get_plugin_config(self.device, generic_name="device_config") def to_pod(self): pod = super(RunConfiguration, self).to_pod() - pod['device_config'] = self.device_config + pod['device_config'] = dict(self.device_config or {}) return pod - # pylint: disable=no-member @classmethod - def from_pod(cls, pod, plugin_cache): - try: - device_config = obj_dict(values=pod.pop("device_config"), not_in_dict=['name']) - except KeyError as e: - msg = 'No value specified for mandatory parameter "{}".' - raise ConfigError(msg.format(e.args[0])) + def from_pod(cls, pod): + meta_pod = {} + for cfg_point in cls.meta_data: + meta_pod[cfg_point.name] = pod.pop(cfg_point.name, None) - instance = super(RunConfiguration, cls).from_pod(pod, plugin_cache) + instance = super(RunConfiguration, cls).from_pod(pod) + for cfg_point in cls.meta_data: + cfg_point.set_value(instance, meta_pod[cfg_point.name]) - device_config.name = "device_config" - cfg_points = plugin_cache.get_plugin_parameters(instance.device) - for entry_name in device_config.iterkeys(): - if entry_name not in cfg_points.iterkeys(): - msg = 'Invalid entry "{}" for device "{}".' - raise ConfigError(msg.format(entry_name, instance.device, cls.name)) - else: - cfg_points[entry_name].validate(device_config) - - instance.device_config = device_config return instance -# This is the configuration for WA jobs class JobSpec(Configuration): name = "Job Spec" @@ -795,6 +796,23 @@ class JobSpec(Configuration): description=''' The name of the workload to run. '''), + ConfigurationPoint('workload_parameters', kind=obj_dict, + aliases=["params", "workload_params"], + description=''' + Parameter to be passed to the workload + '''), + ConfigurationPoint('runtime_parameters', kind=obj_dict, + aliases=["runtime_params"], + description=''' + Runtime parameters to be set prior to running + the workload. + '''), + ConfigurationPoint('boot_parameters', kind=obj_dict, + aliases=["boot_params"], + description=''' + Parameters to be used when rebooting the target + prior to running the workload. + '''), ConfigurationPoint('label', kind=str, description=''' Similar to IDs but do not have the uniqueness restriction. @@ -823,14 +841,23 @@ class JobSpec(Configuration): ] configuration = {cp.name: cp for cp in config_points} + @classmethod + def from_pod(cls, pod): + job_id = pod.pop('id') + instance = super(JobSpec, cls).from_pod(pod) + instance['id'] = job_id + return instance + def __init__(self): super(JobSpec, self).__init__() self.to_merge = defaultdict(OrderedDict) self._sources = [] self.id = None - self.workload_parameters = None - self.runtime_parameters = None - self.boot_parameters = None + + def to_pod(self): + pod = super(JobSpec, self).to_pod() + pod['id'] = self.id + return pod def update_config(self, source, check_mandatory=True): self._sources.append(source) @@ -848,7 +875,6 @@ class JobSpec(Configuration): msg = 'Error in {}:\n\t{}' raise ConfigError(msg.format(source.name, e.message)) - def merge_workload_parameters(self, plugin_cache): # merge global generic and specific config workload_params = plugin_cache.get_plugin_config(self.workload_name, @@ -876,7 +902,10 @@ class JobSpec(Configuration): # Order global runtime parameters runtime_parameters = OrderedDict() - global_runtime_params = plugin_cache.get_plugin_config("runtime_parameters") + try: + global_runtime_params = plugin_cache.get_plugin_config("runtime_parameters") + except NotFoundError: + global_runtime_params = {} for source in plugin_cache.sources: runtime_parameters[source] = global_runtime_params[source] @@ -890,27 +919,6 @@ class JobSpec(Configuration): def finalize(self): self.id = "-".join([source.config['id'] for source in self._sources[1:]]) # ignore first id, "global" - def to_pod(self): - pod = super(JobSpec, self).to_pod() - pod['workload_parameters'] = self.workload_parameters - pod['runtime_parameters'] = self.runtime_parameters - pod['boot_parameters'] = self.boot_parameters - return pod - - @classmethod - def from_pod(cls, pod, plugin_cache): - try: - workload_parameters = pod['workload_parameters'] - runtime_parameters = pod['runtime_parameters'] - boot_parameters = pod['boot_parameters'] - except KeyError as e: - msg = 'No value specified for mandatory parameter "{}}".' - raise ConfigError(msg.format(e.args[0])) - - instance = super(JobSpec, cls).from_pod(pod, plugin_cache) - - # TODO: validate parameters and construct the rest of the instance - # This is used to construct the list of Jobs WA will run class JobGenerator(object): @@ -970,6 +978,7 @@ class JobGenerator(object): self.ids_to_run = ids def generate_job_specs(self, target_manager): + specs = [] for leaf in self.root_node.leaves(): workload_entries = leaf.workload_entries sections = [leaf] @@ -978,18 +987,23 @@ class JobGenerator(object): sections.insert(0, ancestor) for workload_entry in workload_entries: - job_spec = create_job_spec(workload_entry, sections, target_manager) - for job_id in self.ids_to_run: - if job_id in job_spec.id: - break - else: - continue + job_spec = create_job_spec(workload_entry, sections, + target_manager, self.plugin_cache, + self.disabled_instruments) + if self.ids_to_run: + for job_id in self.ids_to_run: + if job_id in job_spec.id: + break + else: + continue self.update_enabled_instruments(job_spec.instrumentation.values()) - yield job_spec + specs.append(job_spec) + return specs -def create_job_spec(workload_entry, sections, target_manager): +def create_job_spec(workload_entry, sections, target_manager, plugin_cache, + disabled_instruments): job_spec = JobSpec() # PHASE 2.1: Merge general job spec configuration @@ -998,18 +1012,17 @@ def create_job_spec(workload_entry, sections, target_manager): job_spec.update_config(workload_entry, check_mandatory=False) # PHASE 2.2: Merge global, section and workload entry "workload_parameters" - job_spec.merge_workload_parameters(self.plugin_cache) - target_manager.static_runtime_parameter_validation(job_spec.runtime_parameters) + job_spec.merge_workload_parameters(plugin_cache) # TODO: PHASE 2.3: Validate device runtime/boot paramerers - job_spec.merge_runtime_parameters(self.plugin_cache, target_manager) + job_spec.merge_runtime_parameters(plugin_cache, target_manager) target_manager.validate_runtime_parameters(job_spec.runtime_parameters) # PHASE 2.4: Disable globally disabled instrumentation - job_spec.set("instrumentation", self.disabled_instruments) + job_spec.set("instrumentation", disabled_instruments) job_spec.finalize() return job_spec -settings = WAConfiguration(os.environ) +settings = MetaConfiguration(os.environ) diff --git a/wlauto/core/configuration/default.py b/wlauto/core/configuration/default.py index 0d520ca9..5145a6b4 100644 --- a/wlauto/core/configuration/default.py +++ b/wlauto/core/configuration/default.py @@ -1,4 +1,4 @@ -from wlauto.core.configuration.configuration import WAConfiguration, RunConfiguration +from wlauto.core.configuration.configuration import MetaConfiguration, RunConfiguration from wlauto.core.configuration.plugin_cache import PluginCache from wlauto.utils.serializer import yaml from wlauto.utils.doc import strip_inlined_text @@ -33,7 +33,7 @@ def _format_instruments(output): def generate_default_config(path): with open(path, 'w') as output: - for param in WAConfiguration.config_points + RunConfiguration.config_points: + for param in MetaConfiguration.config_points + RunConfiguration.config_points: entry = {param.name: param.default} comment = _format_yaml_comment(param) output.writelines(comment) diff --git a/wlauto/core/state.py b/wlauto/core/configuration/manager.py similarity index 54% rename from wlauto/core/state.py rename to wlauto/core/configuration/manager.py index 9071aa93..ef41be9f 100644 --- a/wlauto/core/state.py +++ b/wlauto/core/configuration/manager.py @@ -1,10 +1,29 @@ -from wlauto.core.configuration.configuration import (RunConfiguration, +from wlauto.core.configuration.configuration import (MetaConfiguration, + RunConfiguration, JobGenerator, settings) from wlauto.core.configuration.parsers import ConfigParser from wlauto.core.configuration.plugin_cache import PluginCache -class WAState(object): +class CombinedConfig(object): + + @staticmethod + def from_pod(pod): + instance = CombinedConfig() + instance.settings = MetaConfiguration.from_pod(pod.get('setttings', {})) + instance.run_config = RunConfiguration.from_pod(pod.get('run_config', {})) + return instance + + def __init__(self, settings=None, run_config=None): + self.settings = settings + self.run_config = run_config + + def to_pod(self): + return {'settings': self.settings.to_pod(), + 'run_config': self.run_config.to_pod()} + + +class ConfigManager(object): """ Represents run-time state of WA. Mostly used as a container for loaded configuration and discovered plugins. @@ -20,6 +39,8 @@ class WAState(object): self.jobs_config = JobGenerator(self.plugin_cache) self.loaded_config_sources = [] self._config_parser = ConfigParser() + self._job_specs = [] + self.jobs = [] def load_config_file(self, filepath): self._config_parser.load_from_path(self, filepath) @@ -29,4 +50,7 @@ class WAState(object): self._config_parser.load(self, values, source) self.loaded_config_sources.append(source) + def finalize(self): + self.run_config.merge_device_config(self.plugin_cache) + return CombinedConfig(self.settings, self.run_config) diff --git a/wlauto/core/device_manager.py b/wlauto/core/device_manager.py index 3e8d9296..ca0c3a68 100644 --- a/wlauto/core/device_manager.py +++ b/wlauto/core/device_manager.py @@ -26,7 +26,8 @@ class TargetInfo(object): instance.os_version = pod['os_version'] instance.abi = pod['abi'] instance.is_rooted = pod['is_rooted'] - instance.kernel_version = KernelVersion(pod['kernel_version']) + instance.kernel_version = KernelVersion(pod['kernel_release'], + pod['kernel_version']) instance.kernel_config = KernelConfig(pod['kernel_config']) if pod["target"] == "AndroidTarget": @@ -69,15 +70,16 @@ class TargetInfo(object): def to_pod(self): pod = {} - pod['target'] = self.target.__class__.__name__ + pod['target'] = self.target pod['abi'] = self.abi - pod['cpuinfo'] = self.cpuinfo.text + pod['cpuinfo'] = self.cpuinfo.sections pod['os'] = self.os pod['os_version'] = self.os_version pod['abi'] = self.abi pod['is_rooted'] = self.is_rooted + pod['kernel_release'] = self.kernel_version.release pod['kernel_version'] = self.kernel_version.version - pod['kernel_config'] = self.kernel_config.text + pod['kernel_config'] = dict(self.kernel_config.iteritems()) if self.target == "AndroidTarget": pod['screen_resolution'] = self.screen_resolution diff --git a/wlauto/core/entry_point.py b/wlauto/core/entry_point.py index d58f1cc8..d3dea0f0 100644 --- a/wlauto/core/entry_point.py +++ b/wlauto/core/entry_point.py @@ -24,8 +24,8 @@ import warnings from wlauto.core import pluginloader from wlauto.core.command import init_argument_parser from wlauto.core.configuration import settings +from wlauto.core.configuration.manager import ConfigManager from wlauto.core.host import init_user_directory -from wlauto.core.state import WAState from wlauto.exceptions import WAError, DevlibError, ConfigError from wlauto.utils.doc import format_body from wlauto.utils.log import init_logging @@ -46,7 +46,7 @@ def load_commands(subparsers): def main(): - state = WAState() + config = ConfigManager() if not os.path.exists(settings.user_directory): init_user_directory() @@ -68,16 +68,16 @@ def main(): settings.set("verbosity", args.verbose) - state.load_config_file(settings.user_config_file) + config.load_config_file(settings.user_config_file) for config_file in args.config: if not os.path.exists(config_file): raise ConfigError("Config file {} not found".format(config_file)) - state.load_config_file(config_file) + config.load_config_file(config_file) init_logging(settings.verbosity) command = commands[args.command] - sys.exit(command.execute(state, args)) + sys.exit(command.execute(config, args)) except KeyboardInterrupt: logging.info('Got CTRL-C. Aborting.') diff --git a/wlauto/core/execution.py b/wlauto/core/execution.py index 30477984..95d67602 100644 --- a/wlauto/core/execution.py +++ b/wlauto/core/execution.py @@ -51,6 +51,7 @@ import wlauto.core.signal as signal from wlauto.core import instrumentation from wlauto.core import pluginloader from wlauto.core.configuration import settings +from wlauto.core.device_manager import TargetInfo from wlauto.core.plugin import Artifact from wlauto.core.resolver import ResourceResolver from wlauto.core.result import ResultManager, IterationResult, RunResult @@ -213,6 +214,30 @@ def _check_artifact_path(path, rootpath): return full_path + +class FakeTargetManager(object): + + def __init__(self, name, config): + self.device_name = name + self.device_config = config + + from devlib import LocalLinuxTarget + self.target = LocalLinuxTarget({'unrooted': True}) + + def get_target_info(self): + return TargetInfo(self.target) + + def validate_runtime_parameters(self, params): + pass + + def merge_runtime_parameters(self, params): + pass + + +def init_target_manager(config): + return FakeTargetManager(config.device, config.device_config) + + class Executor(object): """ The ``Executor``'s job is to set up the execution context and pass to a @@ -237,14 +262,14 @@ class Executor(object): self.device = None self.context = None - def execute(self, state, output): + def execute(self, config_manager, output): """ Execute the run specified by an agenda. Optionally, selectors may be used to only selecute a subset of the specified agenda. Params:: - :state: a ``WAState`` containing processed configuraiton + :state: a ``ConfigManager`` containing processed configuraiton :output: an initialized ``RunOutput`` that will be used to store the results. @@ -253,8 +278,17 @@ class Executor(object): signal.connect(self._warning_signalled_callback, signal.WARNING_LOGGED) self.logger.info('Initializing run') + self.logger.debug('Finalizing run configuration.') + config = config_manager.finalize() + output.write_config(config) - self.logger.debug('Loading run configuration.') + self.logger.info('Connecting to target') + target_manager = init_target_manager(config.run_config) + output.write_target_info(target_manager.get_target_info()) + + self.logger.info('Generationg jobs') + job_specs = config_manager.jobs_config.generate_job_specs(target_manager) + output.write_job_specs(job_specs) def old_exec(self, agenda, selectors={}): self.config.set_agenda(agenda, selectors) diff --git a/wlauto/core/output.py b/wlauto/core/output.py index 183ce8ed..1047bf2f 100644 --- a/wlauto/core/output.py +++ b/wlauto/core/output.py @@ -6,6 +6,9 @@ import sys import uuid from copy import copy +from wlauto.core.configuration.configuration import JobSpec +from wlauto.core.configuration.manager import ConfigManager +from wlauto.core.device_manager import TargetInfo from wlauto.utils.misc import touch from wlauto.utils.serializer import write_pod, read_pod @@ -78,6 +81,18 @@ class RunOutput(object): def statefile(self): return os.path.join(self.basepath, '.run_state.json') + @property + def configfile(self): + return os.path.join(self.metadir, 'config.json') + + @property + def targetfile(self): + return os.path.join(self.metadir, 'target_info.json') + + @property + def jobsfile(self): + return os.path.join(self.metadir, 'jobs.json') + def __init__(self, path): self.basepath = path self.info = None @@ -98,6 +113,32 @@ class RunOutput(object): def write_state(self): write_pod(self.state.to_pod(), self.statefile) + def write_config(self, config): + write_pod(config.to_pod(), self.configfile) + + def read_config(self): + if not os.path.isfile(self.configfile): + return None + return ConfigManager.from_pod(read_pod(self.configfile)) + + def write_target_info(self, ti): + write_pod(ti.to_pod(), self.targetfile) + + def read_config(self): + if not os.path.isfile(self.targetfile): + return None + return TargetInfo.from_pod(read_pod(self.targetfile)) + + def write_job_specs(self, job_specs): + job_specs[0].to_pod() + js_pod = {'jobs': [js.to_pod() for js in job_specs]} + write_pod(js_pod, self.jobsfile) + + def read_job_specs(self): + if not os.path.isfile(self.jobsfile): + return None + pod = read_pod(self.jobsfile) + return [JobSpec.from_pod(jp) for jp in pod['jobs']] def init_wa_output(path, wa_state, force=False): diff --git a/wlauto/tests/test_parsers.py b/wlauto/tests/test_parsers.py index 763d2c7f..77d8bba4 100644 --- a/wlauto/tests/test_parsers.py +++ b/wlauto/tests/test_parsers.py @@ -9,7 +9,7 @@ from wlauto.exceptions import ConfigError from wlauto.core.configuration.parsers import * # pylint: disable=wildcard-import from wlauto.core.configuration.parsers import _load_file, _collect_valid_id, _resolve_params_alias from wlauto.core.configuration import RunConfiguration, JobGenerator, PluginCache, ConfigurationPoint -from wlauto.core.configuration.configuration import WAConfiguration +from wlauto.core.configuration.configuration import MetaConfiguration from wlauto.utils.types import toggle_set, reset_counter @@ -129,8 +129,8 @@ class TestFunctions(TestCase): class TestConfigParser(TestCase): def test_error_cases(self): - wa_config = Mock(spec=WAConfiguration) - wa_config.configuration = WAConfiguration.configuration + wa_config = Mock(spec=MetaConfiguration) + wa_config.configuration = MetaConfiguration.configuration run_config = Mock(spec=RunConfiguration) run_config.configuration = RunConfiguration.configuration config_parser = ConfigParser(wa_config, @@ -155,8 +155,8 @@ class TestConfigParser(TestCase): "Unit test") def test_config_points(self): - wa_config = Mock(spec=WAConfiguration) - wa_config.configuration = WAConfiguration.configuration + wa_config = Mock(spec=MetaConfiguration) + wa_config.configuration = MetaConfiguration.configuration run_config = Mock(spec=RunConfiguration) run_config.configuration = RunConfiguration.configuration @@ -211,8 +211,8 @@ class TestAgendaParser(TestCase): # Tests Phase 1 & 2 def test_valid_structures(self): - wa_config = Mock(spec=WAConfiguration) - wa_config.configuration = WAConfiguration.configuration + wa_config = Mock(spec=MetaConfiguration) + wa_config.configuration = MetaConfiguration.configuration run_config = Mock(spec=RunConfiguration) run_config.configuration = RunConfiguration.configuration jobs_config = Mock(spec=JobGenerator) @@ -241,8 +241,8 @@ class TestAgendaParser(TestCase): # Test Phase 3 def test_id_collection(self): - wa_config = Mock(spec=WAConfiguration) - wa_config.configuration = WAConfiguration.configuration + wa_config = Mock(spec=MetaConfiguration) + wa_config.configuration = MetaConfiguration.configuration run_config = Mock(spec=RunConfiguration) run_config.configuration = RunConfiguration.configuration jobs_config = Mock(spec=JobGenerator) @@ -267,8 +267,8 @@ class TestAgendaParser(TestCase): # Test Phase 4 def test_id_assignment(self): - wa_config = Mock(spec=WAConfiguration) - wa_config.configuration = WAConfiguration.configuration + wa_config = Mock(spec=MetaConfiguration) + wa_config.configuration = MetaConfiguration.configuration run_config = Mock(spec=RunConfiguration) run_config.configuration = RunConfiguration.configuration jobs_config = Mock(spec=JobGenerator) @@ -362,7 +362,7 @@ class TestAgendaParser(TestCase): class TestCommandLineArgsParser(TestCase): - wa_config = Mock(spec=WAConfiguration) + wa_config = Mock(spec=MetaConfiguration) run_config = Mock(spec=RunConfiguration) jobs_config = Mock(spec=JobGenerator) diff --git a/wlauto/utils/types.py b/wlauto/utils/types.py index c23f8215..8e4c5e65 100644 --- a/wlauto/utils/types.py +++ b/wlauto/utils/types.py @@ -322,7 +322,8 @@ class prioritylist(object): raise ValueError('Invalid index {}'.format(index)) current_global_offset = 0 priority_counts = {priority: count for (priority, count) in - zip(self.priorities, [len(self.elements[p]) for p in self.priorities])} + zip(self.priorities, [len(self.elements[p]) + for p in self.priorities])} for priority in self.priorities: if not index_range: break @@ -351,13 +352,9 @@ class toggle_set(set): and ``cherries`` but disables ``oranges``. """ - def merge_with(self, other): - new_self = copy(self) - return toggle_set.merge(other, new_self) - - def merge_into(self, other): - other = copy(other) - return toggle_set.merge(self, other) + @staticmethod + def from_pod(pod): + return toggle_set(pod) @staticmethod def merge(source, dest): @@ -372,6 +369,14 @@ class toggle_set(set): dest.add(item) return dest + def merge_with(self, other): + new_self = copy(self) + return toggle_set.merge(other, new_self) + + def merge_into(self, other): + other = copy(other) + return toggle_set.merge(self, other) + def values(self): """ returns a list of enabled items. @@ -396,6 +401,9 @@ class toggle_set(set): conflicts.append(item) return conflicts + def to_pod(self): + return list(self.values()) + class ID(str): def merge_with(self, other): @@ -411,11 +419,19 @@ class obj_dict(MutableMapping): as an attribute. :param not_in_dict: A list of keys that can only be accessed as attributes + """ - def __init__(self, not_in_dict=None, values={}): + @staticmethod + def from_pod(pod): + return obj_dict(pod) + + def __init__(self, values=None, not_in_dict=None): + self.__dict__['dict'] = dict(values or {}) self.__dict__['not_in_dict'] = not_in_dict if not_in_dict is not None else [] - self.__dict__['dict'] = dict(values) + + def to_pod(self): + return self.__dict__['dict'] def __getitem__(self, key): if key in self.not_in_dict: @@ -457,13 +473,3 @@ class obj_dict(MutableMapping): return self.__dict__['dict'][name] else: raise AttributeError("No such attribute: " + name) - - def to_pod(self): - return self.__dict__.copy() - - @staticmethod - def from_pod(pod): - instance = ObjDict() - for k, v in pod.iteritems(): - instance[k] = v - return instance