diff options
author | Guillaume Tucker <guillaume.tucker@collabora.com> | 2019-08-29 19:55:19 +0100 |
---|---|---|
committer | Guillaume Tucker <guillaume.tucker@collabora.com> | 2019-09-04 12:49:44 +0100 |
commit | d53947c4acbb4f8ea8a23f5aa7ec85f6b8826d12 (patch) | |
tree | 513deb624dd5581d116481399537b974daf9dd67 /kernelci/configs.py | |
parent | 800c16253069a9e555b282038b310a7061e165a4 (diff) |
kernelci.configs: convert to kernelci.config.{build,test}
* convert kernelci.configs into a new kernelci.config package with
kernelci.config.build and kernelci.config.test
* update kci_build and lava-v2-jobs-from-api.py accordingly
* update unit tests accordingly
Signed-off-by: Guillaume Tucker <guillaume.tucker@collabora.com>
Diffstat (limited to 'kernelci/configs.py')
-rw-r--r-- | kernelci/configs.py | 952 |
1 files changed, 0 insertions, 952 deletions
diff --git a/kernelci/configs.py b/kernelci/configs.py deleted file mode 100644 index 9943aed..0000000 --- a/kernelci/configs.py +++ /dev/null @@ -1,952 +0,0 @@ -# Copyright (C) 2018, 2019 Collabora Limited -# Author: Guillaume Tucker <guillaume.tucker@collabora.com> -# -# This module is free software; you can redistribute it and/or modify it under -# the terms of the GNU Lesser General Public License as published by the Free -# Software Foundation; either version 2.1 of the License, or (at your option) -# any later version. -# -# This library is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library; if not, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import re -import yaml - - -# ----------------------------------------------------------------------------- -# Common classes for all config types -# - -class YAMLObject(object): - """Base class with helper methods to initialise objects from YAML data.""" - - @classmethod - def _kw_from_yaml(cls, data, args): - """Create some keyword arguments based on a YAML dictionary - - Return a dictionary suitable to be used as Python keyword arguments in - an object constructor using values from some YAML *data*. The *args* - is a list of keys to look up from the *data* and convert to a - dictionary. Keys that are not in the YAML data are simply omitted from - the returned keywords, relying on default values in object - constructors. - """ - return { - k: v for k, v in ((k, data.get(k)) for k in args) if v - } if data else dict() - - -class Filter(object): - """Base class to implement arbitrary configuration filters.""" - - def __init__(self, items): - """The *items* can be any data used to filter configurations.""" - self._items = items - - def match(self, **kw): - """Return True if the given *kw* keywords match the filter.""" - raise NotImplementedError("Filter.match() is not implemented") - - -class Blacklist(Filter): - """Blacklist filter to discard certain configurations. - - Blacklist *items* are a dictionary associating keys with lists of values. - Any configuration with a key-value pair present in these lists will be - rejected. - """ - - def match(self, **kw): - for k, v in kw.iteritems(): - bl = self._items.get(k) - if not bl: - continue - if any(x in v for x in bl): - return False - - return True - - -class Whitelist(Filter): - """Whitelist filter to only accept certain configurations. - - Whitelist *items* are a dictionary associating keys with lists of values. - For a configuration to be accepted, there must be a value found in each of - these lists. - """ - - def match(self, **kw): - for k, wl in self._items.iteritems(): - v = kw.get(k) - if not v: - return False - if not any(x in v for x in wl): - return False - - return True - - -class Regex(Filter): - """Regex filter to only accept certain configurations. - - Regex *items* are a dictionary associating keys with regular expressions. - The should be one regular expression for each key, not a list of them. For - a configuration to be accepted, its value must match the regular expression - for each key specified in the filter items. - """ - - def __init__(self, *args, **kw): - super(Regex, self).__init__(*args, **kw) - self._re_items = {k: re.compile(v) for k, v in self._items.iteritems()} - - - def match(self, **kw): - for k, r in self._re_items.iteritems(): - v = kw.get(k) - return v and r.match(v) - - -class Combination(Filter): - """Combination filter to only accept some combined configurations. - - Combination *items* are a dictionary with 'keys' and 'values'. The 'keys' - are a list of keywords to look for, and 'values' are a list of combined - values for the given keys. The length of each 'values' item must therefore - match the length of the 'keys' list, and the order of the values must match - the order of the keys. - """ - - def __init__(self, items): - self._keys = tuple(items['keys']) - self._values = list(tuple(values) for values in items['values']) - - def match(self, **kw): - filter_values = tuple(kw.get(k) for k in self._keys) - return filter_values in self._values - - -class FilterFactory(YAMLObject): - """Factory to create filters from YAML data.""" - - _classes = { - 'blacklist': Blacklist, - 'whitelist': Whitelist, - 'regex': Regex, - 'combination': Combination, - } - - @classmethod - def from_yaml(cls, filter_params): - """Iterate through the YAML filters and return Filter objects.""" - filter_list = [] - for f in filter_params: - for filter_type, items in f.iteritems(): - filter_cls = cls._classes[filter_type] - filter_list.append(filter_cls(items)) - return filter_list - - @classmethod - def from_data(cls, data, default_filters=None): - """Look for filters in YAML *data* or return *default_filters*. - - Look for a *filters* element in the YAML *data* dictionary. If there - is one, iterate over each item to return a list of Filter objects. - Otherwise, return *default_filters*. - """ - params = data.get('filters') - return cls.from_yaml(params) if params else default_filters - - -# ----------------------------------------------------------------------------- -# Build configs -# - -class Tree(YAMLObject): - """Kernel git tree model.""" - - def __init__(self, name, url): - """A kernel git tree is essentially a repository with kernel branches. - - *name* is the name of the tree, such as "mainline" or "next". - *url* is the git remote URL for the tree. - """ - self._name = name - self._url = url - - @classmethod - def from_yaml(cls, config, name): - kw = { - 'name': name, - } - kw.update(cls._kw_from_yaml(config, ['url', 'name'])) - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def url(self): - return self._url - - -class Fragment(YAMLObject): - """Kernel config fragment model.""" - - def __init__(self, name, path, configs=None, defconfig=None): - """A kernel config fragment is a list of config options in file. - - *name* is the name of the config fragment so it can be referred to in - other configuration objects. - - *path* is the path where the config fragment either can be found, - either from the git checkout or after being generated. - - *configs* is an optional list of kernel configs to use when generating - a config fragment that does not exist in the git checkout. - - *defconfig* is an optional defconfig name to use as a make target - instead of a real config path. This is only used for - special cases such as the tiny.config fragment which needs - to be built with the tinyconfig make target. - """ - self._name = name - self._path = path - self._configs = configs or list() - self._defconfig = defconfig - - @classmethod - def from_yaml(cls, config, name): - kw = { - 'name': name, - } - kw.update(cls._kw_from_yaml(config, [ - 'name', 'path', 'configs', 'defconfig', - ])) - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def path(self): - return self._path - - @property - def configs(self): - return list(self._configs) - - @property - def defconfig(self): - return self._defconfig - - -class Architecture(YAMLObject): - """CPU architecture attributes.""" - - def __init__(self, name, base_defconfig='defconfig', extra_configs=None, - fragments=None, filters=None): - """Particularities to build kernels for each CPU architecture. - - *name* is the CPU architecture name as per the kernel's convention. - - *base_defconfig* is the defconfig used by default and as a basis when - adding fragments. - - *extra_configs* is a list of extra defconfigs and make targets to - build, for example allnoconfig, allmodconfig and any - arbitrary some_defconfig+CONFIG_XXX=y definitions. - - *fragments* is a list of CPU-specific config fragments to build if - present. - - *filters* is a list of filters to limit the number of builds, typically - using a list of defconfigs to blacklist or whitelist. - """ - self._name = name - self._base_defconfig = base_defconfig - self._extra_configs = extra_configs or [] - self._fragments = fragments or [] - self._filters = filters or list() - - @classmethod - def from_yaml(cls, data, name, fragments): - kw = { - 'name': name, - } - kw.update(cls._kw_from_yaml(data, [ - 'name', 'base_defconfig', 'extra_configs', - ])) - cf = data.get('fragments') - kw['fragments'] = [fragments[name] for name in cf] if cf else None - kw['filters'] = FilterFactory.from_data(data) - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def base_defconfig(self): - return self._base_defconfig - - @property - def extra_configs(self): - return list(self._extra_configs) - - @property - def fragments(self): - return list(self._fragments) - - def match(self, params): - return all(f.match(**params) for f in self._filters) - - -class BuildEnvironment(YAMLObject): - """Kernel build environment model.""" - - def __init__(self, name, cc, cc_version, arch_map=None, - cross_compile=None): - """A build environment is a compiler and tools to build a kernel. - - *name* is the name of the build environment so it can be referred to in - other parts of the build configuration. Typical build - environment names include the compiler type and version such as - "gcc-7" although this is entirely arbitrary. - - *cc* is the compiler type, such as "gcc" or "clang". This is - functional and indicates the actual compiler binary being used. - - *cc_version* is the full version of the compiler. - - *arch_map* is a dictionary mapping kernel CPU architecture names to - ones used in compiler names. For example, gcc compilers are - the same "x86" for both "i386" and "x86_64" kernel - architectures. - - *cross_compile* is a dictionary mapping kernel CPU architecture names - to cross-compiler prefixes. - """ - self._name = name - self._cc = cc - self._cc_version = str(cc_version) - self._arch_map = arch_map or dict() - self._cross_compile = cross_compile or dict() - - @classmethod - def from_yaml(cls, config, name): - kw = { - 'name': name, - } - kw.update(cls._kw_from_yaml( - config, ['name', 'cc', 'cc_version', 'arch_map', 'cross_compile'])) - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def cc(self): - return self._cc - - @property - def cc_version(self): - return self._cc_version - - def get_arch_name(self, kernel_arch): - return self._arch_map.get(kernel_arch, kernel_arch) - - def get_cross_compile(self, kernel_arch): - return self._cross_compile.get(kernel_arch, '') - - -class BuildVariant(YAMLObject): - """A variant of a given build configuration.""" - - def __init__(self, name, architectures, build_environment, fragments=None): - """A build variant is a sub-section of a build configuration. - - *name* is the name of the build variant. It is arbitrary and defined - to be able to refer to the build variant in other parts of the - build configurations or the code using it. - - *architectures* is a list of Architecture objects. There can only be - one Architecture object for any given kernel CPU - architecture name. This list defines the architectures - that should be built for a given build variant. - - *build_environment" is a BuildEnvironment object, to define which - compiler to use to build the kernels. - - *fragments* is an optional list of Fragment objects to define fragments - to build with this build variant. - """ - self._name = name - self._architectures = {arch.name: arch for arch in architectures} - self._build_environment = build_environment - self._fragments = fragments or list() - - @classmethod - def from_yaml(cls, config, name, fragments, build_environments): - kw = { - 'name': name, - } - kw.update(cls._kw_from_yaml( - config, ['name', 'build_environment', 'fragments'])) - kw['build_environment'] = build_environments[kw['build_environment']] - kw['architectures'] = list( - Architecture.from_yaml(data or {}, name, fragments) - for name, data in config['architectures'].iteritems() - ) - cf = kw.get('fragments') - kw['fragments'] = [fragments[name] for name in cf] if cf else None - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def arch_list(self): - return self._architectures.keys() - - @property - def architectures(self): - return list(self._architectures.values()) - - def get_arch(self, arch_name): - return self._architectures.get(arch_name) - - @property - def build_environment(self): - return self._build_environment - - @property - def fragments(self): - return list(self._fragments) - - -class BuildConfig(YAMLObject): - """Build configuration model.""" - - def __init__(self, name, tree, branch, variants): - """A build configuration defines the actual kernels to be built. - - *name* is the name of the build configuration. It is arbitrary and - used in other places to refer to the build configuration. - - *tree* is a Tree object, where the kernel branche to be built can be - found. - - *branch* is the name of the branch to build. There can only be one - branch in each BuildConfig object. - - *variants* is a list of BuildVariant objects, to define all the - variants to build for this tree / branch combination. - """ - self._name = name - self._tree = tree - self._branch = branch - self._variants = variants - - @classmethod - def from_yaml(cls, config, name, trees, fragments, build_envs, defaults): - kw = { - 'name': name, - } - kw.update(cls._kw_from_yaml( - config, ['name', 'tree', 'branch'])) - kw['tree'] = trees[kw['tree']] - config_variants = config.get('variants', defaults) - variants = [ - BuildVariant.from_yaml(variant, name, fragments, build_envs) - for name, variant in config_variants.iteritems() - ] - kw['variants'] = {v.name: v for v in variants} - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def tree(self): - return self._tree - - @property - def branch(self): - return self._branch - - @property - def variants(self): - return list(self._variants.values()) - - def get_variant(self, name): - return self._variants[name] - - -# ----------------------------------------------------------------------------- -# Test configs -# - -class DeviceType(YAMLObject): - """Device type model.""" - - def __init__(self, name, mach, arch, boot_method, dtb=None, base_name=None, - flags=None, filters=None, context=None, params=None): - """A device type describes a category of equivalent hardware devices. - - *name* is unique for the device type, typically as used by LAVA. - *mach* is the name of the SoC manufacturer. - *arch* is the CPU architecture following the Linux kernel convention. - *boot_method* is the name of the boot method to use. - *dtb* is an optional name for a device tree binary. - *base_name* is the name of the base device type used in test labs. - *flags* is a list of optional arbitrary strings. - *filters* is a list of Filter objects associated with this device type. - *context* is an arbirary dictionary used when scheduling tests. - *params* is a dictionary with parameters to pass to the test job - generator. - """ - self._name = name - self._mach = mach - self._arch = arch - self._boot_method = boot_method - self._dtb = dtb - self._base_name = base_name or name - self._params = params or dict() - self._flags = flags or list() - self._filters = filters or list() - self._context = context or dict() - - def __repr__(self): - return self.name - - @property - def name(self): - return self._name - - @property - def base_name(self): - return self._base_name - - @property - def mach(self): - return self._mach - - @property - def arch(self): - return self._arch - - @property - def boot_method(self): - return self._boot_method - - @property - def dtb(self): - return self._dtb - - @property - def params(self): - return dict(self._params) - - @property - def context(self): - return self._context - - def get_flag(self, name): - return name in self._flags - - def match(self, flags, config): - """Checks if the given *flags* and *config* match this device type.""" - return ( - all(not v or self.get_flag(k) for k, v in flags.iteritems()) and - all(f.match(**config) for f in self._filters) - ) - - -class DeviceType_arc(DeviceType): - - def __init__(self, name, mach, arch='arc', *args, **kw): - """arc device type with a device tree.""" - kw.setdefault('dtb', '{}.dtb'.format(name)) - super(DeviceType_arc, self).__init__(name, mach, arch, *args, **kw) - - -class DeviceType_arm(DeviceType): - - def __init__(self, name, mach, arch='arm', *args, **kw): - """arm device type with a device tree.""" - kw.setdefault('dtb', '{}.dtb'.format(name)) - super(DeviceType_arm, self).__init__(name, mach, arch, *args, **kw) - - -class DeviceType_mips(DeviceType): - - def __init__(self, name, mach, arch='mips', *args, **kw): - """mips device type with a device tree.""" - kw.setdefault('dtb', '{}.dtb'.format(name)) - super(DeviceType_mips, self).__init__(name, mach, arch, *args, **kw) - - -class DeviceType_arm64(DeviceType): - - def __init__(self, name, mach, arch='arm64', *args, **kw): - """arm64 device type with a device tree.""" - kw.setdefault('dtb', '{}/{}.dtb'.format(mach, name)) - super(DeviceType_arm64, self).__init__(name, mach, arch, *args, **kw) - - -class DeviceTypeFactory(YAMLObject): - """Factory to create device types from YAML data.""" - - _classes = { - 'arc-dtb': DeviceType_arc, - 'mips-dtb': DeviceType_mips, - 'arm-dtb': DeviceType_arm, - 'arm64-dtb': DeviceType_arm64, - } - - @classmethod - def from_yaml(cls, name, device_type, default_filters=None): - kw = cls._kw_from_yaml(device_type, [ - 'mach', 'arch', 'boot_method', 'dtb', 'flags', 'context', 'params']) - kw.update({ - 'name': name, - 'base_name': device_type.get('base_name'), - 'filters': FilterFactory.from_data(device_type, default_filters), - }) - cls_name = device_type.get('class') - device_cls = cls._classes[cls_name] if cls_name else DeviceType - return device_cls(**kw) - - -class RootFSType(YAMLObject): - """Root file system type model.""" - - def __init__(self, url, arch_dict=None): - """A root file system type covers common file system features. - - *url* is the base URL for file system binaries. Each file system - variant will have some URLs based on this one with various - formats and architectures. - - *arch_dict* is a dictionary to map CPU architecture names following the - kernel convention with distribution architecture names as - used by the file system type. Keys are the names used by - the root file system type (distro), and values are lists of - dictionaries with kernel architecture names and other - properties such as the endinanness. - """ - self._url = url - self._arch_dict = arch_dict or dict() - - @classmethod - def from_yaml(cls, fs_type): - kw = cls._kw_from_yaml(fs_type, ['url']) - arch_map = fs_type.get('arch_map') - if arch_map: - arch_dict = {} - for arch_name, arch_dicts in arch_map.iteritems(): - for d in arch_dicts: - key = tuple((k, v) for (k, v) in d.iteritems()) - arch_dict[key] = arch_name - kw['arch_dict'] = arch_dict - return cls(**kw) - - @property - def url(self): - return self._url - - def get_arch_name(self, arch, endian): - arch_key = ('arch', arch) - endian_key = ('endian', endian) - arch_name = (self._arch_dict.get((arch_key, endian_key)) or - self._arch_dict.get((arch_key,), arch)) - return arch_name - - -class RootFS(YAMLObject): - """Root file system model.""" - - def __init__(self, url_formats, fs_type, boot_protocol='tftp', - root_type=None, prompt="/ #"): - """A root file system is any user-space that can be used in test jobs. - - *url_formats* are a dictionary with a format string for each type of - file system available (ramdisk, nfs...). There is - typically only one entry here for the main *root_type*, - but multiple entries are possible in particular to boot - with first a ramdisk and then pivot to nfs root. - - *fs_type* is a RootFSType instance. - - *boot_protocol* is how the file system is made available to the kernel, - by default `tftp` typically to download a ramdisk. - - *root_type* is the name of the file system type (ramdisk, ...) as used - in the job template naming scheme. - - *prompt* is a string used in the job definition to tell when the - user-space is available to run some commands. - """ - self._url_format = url_formats - self._fs_type = fs_type - self._root_type = root_type or url_formats.keys()[0] - self._boot_protocol = boot_protocol - self._prompt = prompt - self._arch_dict = {} - - @classmethod - def from_yaml(cls, file_system_types, rootfs): - kw = cls._kw_from_yaml(rootfs, [ - 'boot_protocol', 'root_type', 'prompt']) - fs_type = file_system_types[rootfs['type']] - base_url = fs_type.url - kw['fs_type'] = fs_type - kw['url_formats'] = { - fs: '/'.join([base_url, url]) for fs, url in ( - (fs, rootfs.get(fs)) for fs in ['ramdisk', 'nfs']) - if url - } - return cls(**kw) - - @property - def prompt(self): - return self._prompt - - @property - def boot_protocol(self): - return self._boot_protocol - - @property - def root_type(self): - return self._root_type - - def get_url(self, fs_type, arch, endian): - """Get the URL of the file system for the given variant and arch. - - The *fs_type* should match one of the URL patterns known to this root - file system. - """ - fmt = self._url_format.get(fs_type) - if not fmt: - return None - arch_name = self._fs_type.get_arch_name(arch, endian) - return fmt.format(arch=arch_name) - - -class TestPlan(YAMLObject): - """Test plan model.""" - - _pattern = '{plan}/{category}-{method}-{protocol}-{rootfs}-{plan}-template.jinja2' - - def __init__(self, name, rootfs, params=None, category='generic', - filters=None, pattern=None): - """A test plan is an arbitrary group of test cases to be run. - - *name* is the overall arbitrary test plan name, used when looking for - job template files. - - *rootfs* is a RootFS object to be used to run this test plan. - - *params" is a dictionary with parameters to pass to the test job - generator. - - *category* is to classify the type of job to be run, used when looking - for job template files. - - *filters* is a list of Filter objects associated with this test plan. - - *pattern* is a string pattern to create the path to the job template - file, see TestPlan._pattern for the default value with the - regular template file naming scheme. - """ - self._name = name - self._rootfs = rootfs - self._params = params or dict() - self._category = category - self._filters = filters or list() - if pattern: - self._pattern = pattern - - @classmethod - def from_yaml(cls, name, test_plan, file_systems, default_filters=None): - kw = { - 'name': name, - 'rootfs': file_systems[test_plan['rootfs']], - 'filters': FilterFactory.from_data(test_plan, default_filters), - } - kw.update(cls._kw_from_yaml(test_plan, [ - 'name', 'category', 'pattern', 'params'])) - return cls(**kw) - - @property - def name(self): - return self._name - - @property - def rootfs(self): - return self._rootfs - - @property - def params(self): - return dict(self._params) - - def get_template_path(self, boot_method): - """Get the path to the template file for the given *boot_method* - - As different device types use different boot methods (u-boot, grub...), - each test plan can have several template variants to accomodate for - these. All the other parameters are attributes of the test plan. - """ - return self._pattern.format( - category=self._category, - method=boot_method, - protocol=self.rootfs.boot_protocol, - rootfs=self.rootfs.root_type, - plan=self.name) - - def match(self, config): - return all(f.match(**config) for f in self._filters) - - -class TestConfig(YAMLObject): - """Test configuration model.""" - - def __init__(self, device_type, test_plans, filters=None): - """A test configuration has a *device_type* and a list of *test_plans*. - - *device_type* is a DeviceType object. - *test_plans* is a list of TestPlan objects to run on the device type. - """ - self._device_type = device_type - self._test_plans = { - t.name: t for t in test_plans - } - self._filters = filters or list() - - @classmethod - def from_yaml(cls, test_config, device_types, test_plans, - default_filters=None): - kw = { - 'device_type': device_types[test_config['device_type']], - 'test_plans': [test_plans[test] - for test in test_config['test_plans']], - 'filters': FilterFactory.from_data(test_config, default_filters), - } - - return cls(**kw) - - @property - def device_type(self): - return self._device_type - - @property - def test_plans(self): - return self._test_plans - - def match(self, arch, plan, flags, config): - return ( - plan in self._test_plans and - self._test_plans[plan].match(config) and - self.device_type.arch == arch and - self.device_type.match(flags, config) and - all(f.match(**config) for f in self._filters) - ) - - def get_template_path(self, plan): - test_plan = self._test_plans[plan] - return test_plan.get_template_path(self._device_type.boot_method) - - -# ----------------------------------------------------------------------------- -# Entry points -# - -def builds_from_yaml(yaml_path): - with open(yaml_path) as f: - data = yaml.safe_load(f) - - trees = { - name: Tree.from_yaml(config, name) - for name, config in data['trees'].iteritems() - } - - fragments = { - name: Fragment.from_yaml(config, name) - for name, config in data.get('fragments', {}).iteritems() - } - - build_environments = { - name: BuildEnvironment.from_yaml(config, name) - for name, config in data['build_environments'].iteritems() - } - - defaults = data.get('build_configs_defaults', {}) - - build_configs = { - name: BuildConfig.from_yaml(config, name, trees, fragments, - build_environments, defaults) - for name, config in data['build_configs'].iteritems() - } - - config_data = { - 'trees': trees, - 'fragments': fragments, - 'build_environments': build_environments, - 'build_configs': build_configs, - } - - return config_data - - -def tests_from_yaml(yaml_path): - with open(yaml_path) as f: - data = yaml.safe_load(f) - - fs_types = { - name: RootFSType.from_yaml(fs_type) - for name, fs_type in data['file_system_types'].iteritems() - } - - file_systems = { - name: RootFS.from_yaml(fs_types, rootfs) - for name, rootfs in data['file_systems'].iteritems() - } - - plan_filters = FilterFactory.from_yaml(data['test_plan_default_filters']) - - test_plans = { - name: TestPlan.from_yaml(name, test_plan, file_systems, plan_filters) - for name, test_plan in data['test_plans'].iteritems() - } - - device_filters = FilterFactory.from_yaml(data['device_default_filters']) - - device_types = { - name: DeviceTypeFactory.from_yaml(name, device_type, device_filters) - for name, device_type in data['device_types'].iteritems() - } - - test_configs = [ - TestConfig.from_yaml(test_config, device_types, test_plans) - for test_config in data['test_configs'] - ] - - config_data = { - 'file_systems': file_systems, - 'test_plans': test_plans, - 'device_types': device_types, - 'test_configs': test_configs, - } - - return config_data |