未验证 提交 7dfa5a87 编写于 作者: C Cleber Rosa

Merge remote-tracking branch 'ldoktor/mux-rename3'

......@@ -38,7 +38,7 @@ from . import exit_codes
from . import exceptions
from . import job_id
from . import output
from . import multiplexer
from . import varianter
from . import tree
from . import test
from . import jobdata
......@@ -371,9 +371,9 @@ class Job(object):
job_log.info('logs ' + data_dir.get_logs_dir())
job_log.info('')
def _log_mux_tree(self, mux):
def _log_variants_tree(self, variant):
job_log = _TEST_LOGGER
tree_repr = tree.tree_view(mux.variants.root, verbose=True,
tree_repr = tree.tree_view(variant.variants.root, verbose=True,
use_utf8=False)
if tree_repr:
job_log.info('Multiplex tree representation:')
......@@ -386,14 +386,14 @@ class Job(object):
job_log.info('Temporary dir: %s', data_dir.get_tmp_dir())
job_log.info('')
def _log_mux_variants(self, mux):
def _log_variants(self, variant):
job_log = _TEST_LOGGER
for (index, tpl) in enumerate(mux.variants):
for (index, tpl) in enumerate(variant.variants):
paths = ', '.join([x.path for x in tpl])
job_log.info('Variant %s: %s', index + 1, paths)
if mux.variants:
if variant.variants:
job_log.info('')
def _log_job_debug_info(self, mux):
......@@ -404,9 +404,9 @@ class Job(object):
self._log_avocado_version()
self._log_avocado_config()
self._log_avocado_datadir()
self._log_mux_tree(mux)
self._log_variants_tree(mux)
self._log_tmp_dir()
self._log_mux_variants(mux)
self._log_variants(mux)
self._log_job_id()
def create_test_suite(self):
......@@ -446,24 +446,25 @@ class Job(object):
self._result_events_dispatcher.map_method('pre_tests', self)
def run_tests(self):
mux = getattr(self.args, "mux", None)
if mux is None:
mux = multiplexer.Mux()
if not mux.is_parsed(): # Mux not yet parsed, apply args
variant = getattr(self.args, "avocado_variants", None)
if variant is None:
variant = varianter.Varianter()
if not variant.is_parsed(): # Varianter not yet parsed, apply args
try:
mux.parse(self.args)
variant.parse(self.args)
except (IOError, ValueError) as details:
raise exceptions.OptionValidationError("Unable to parse mux: "
"%s" % details)
raise exceptions.OptionValidationError("Unable to parse "
"variant: %s" % details)
self._make_test_runner()
self._start_sysinfo()
self._log_job_debug_info(mux)
jobdata.record(self.args, self.logdir, mux, self.references, sys.argv)
self._log_job_debug_info(variant)
jobdata.record(self.args, self.logdir, variant, self.references,
sys.argv)
replay_map = getattr(self.args, 'replay_map', None)
summary = self.test_runner.run_suite(self.test_suite,
mux,
variant,
self.timeout,
replay_map)
# If it's all good so far, set job status to 'PASS'
......
......@@ -30,7 +30,7 @@ JOB_DATA_FALLBACK_DIR = 'replay'
CONFIG_FILENAME = 'config'
TEST_REFERENCES_FILENAME = 'test_references'
TEST_REFERENCES_FILENAME_LEGACY = 'urls'
MUX_FILENAME = 'multiplex'
VARIANTS_FILENAME = 'multiplex'
PWD_FILENAME = 'pwd'
ARGS_FILENAME = 'args'
CMDLINE_FILENAME = 'cmdline'
......@@ -45,7 +45,7 @@ def record(args, logdir, mux, references=None, cmdline=None):
path_references = os.path.join(base_dir, TEST_REFERENCES_FILENAME)
path_references_legacy = os.path.join(base_dir,
TEST_REFERENCES_FILENAME_LEGACY)
path_mux = os.path.join(base_dir, MUX_FILENAME)
path_mux = os.path.join(base_dir, VARIANTS_FILENAME)
path_pwd = os.path.join(base_dir, PWD_FILENAME)
path_args = os.path.join(base_dir, ARGS_FILENAME)
path_cmdline = os.path.join(base_dir, CMDLINE_FILENAME)
......@@ -105,11 +105,11 @@ def retrieve_references(resultsdir):
return ast.literal_eval(references_file.read())
def retrieve_mux(resultsdir):
def retrieve_variants(resultsdir):
"""
Retrieves the job Mux object from the results directory.
"""
recorded_mux = _retrieve(resultsdir, MUX_FILENAME)
recorded_mux = _retrieve(resultsdir, VARIANTS_FILENAME)
if recorded_mux is None:
return None
with open(recorded_mux, 'r') as mux_file:
......
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program 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 LICENSE for more details.
#
# Copyright: Red Hat Inc. 2016
#
# Authors: Lukas Doktor <ldoktor@redhat.com>
"""
This file contains mux-enabled implementations of parts useful for creating
a custom Varianter plugin.
"""
import re
from . import tree
#
# Multiplex-enabled tree objects
#
REMOVE_NODE = 0
REMOVE_VALUE = 1
class Control(object): # Few methods pylint: disable=R0903
""" Container used to identify node vs. control sequence """
def __init__(self, code, value=None):
self.code = code
self.value = value
class MuxTreeNode(tree.TreeNode):
"""
Class for bounding nodes into tree-structure with support for
multiplexation
"""
def __init__(self, name='', value=None, parent=None, children=None):
super(MuxTreeNode, self).__init__(name, value, parent, children)
self.ctrl = []
self.multiplex = None
def __repr__(self):
return '%s(name=%r)' % (self.__class__.__name__, self.name)
def merge(self, other):
"""
Merges `other` node into this one without checking the name of the
other node. New values are appended, existing values overwritten
and unaffected ones are kept. Then all other node children are
added as children (recursively they get either appended at the end
or merged into existing node in the previous position.
"""
for ctrl in other.ctrl:
if isinstance(ctrl, Control):
if ctrl.code == REMOVE_NODE:
remove = []
regexp = re.compile(ctrl.value)
for child in self.children:
if regexp.match(child.name):
remove.append(child)
for child in remove:
self.children.remove(child)
elif ctrl.code == REMOVE_VALUE:
remove = []
regexp = re.compile(ctrl.value)
for key in self.value.iterkeys():
if regexp.match(key):
remove.append(key)
for key in remove:
self.value.pop(key, None)
super(MuxTreeNode, self).merge(other)
if other.multiplex is True:
self.multiplex = True
elif other.multiplex is False:
self.multiplex = False
class MuxTreeNodeDebug(MuxTreeNode, tree.TreeNodeDebug):
"""
Debug version of TreeNodeDebug
:warning: Origin of the value is appended to all values thus it's not
suitable for running tests.
"""
def __init__(self, name='', value=None, parent=None, children=None,
srcyaml=None):
MuxTreeNode.__init__(self, name, value, parent, children)
tree.TreeNodeDebug.__init__(self, name, value, parent, children,
srcyaml)
#
# Tree filtering
#
def path_parent(path):
"""
From a given path, return its parent path.
:param path: the node path as string.
:return: the parent path as string.
"""
parent = path.rpartition('/')[0]
if not parent:
return '/'
return parent
def apply_filters(root, filter_only=None, filter_out=None):
"""
Apply a set of filters to the tree.
The basic filtering is filter only, which includes nodes,
and the filter out rules, that exclude nodes.
Note that filter_out is stronger than filter_only, so if you filter out
something, you could not bypass some nodes by using a filter_only rule.
:param root: Root node of the multiplex tree.
:param filter_only: the list of paths which will include nodes.
:param filter_out: the list of paths which will exclude nodes.
:return: the original tree minus the nodes filtered by the rules.
"""
if filter_only is None:
filter_only = []
else:
filter_only = [_.rstrip('/') for _ in filter_only if _]
if filter_out is None:
filter_out = []
else:
filter_out = [_.rstrip('/') for _ in filter_out if _]
for node in root.iter_children_preorder():
keep_node = True
for path in filter_only:
if path == '':
continue
if node.path == path:
keep_node = True
break
if node.parent and node.parent.path == path_parent(path):
keep_node = False
continue
for path in filter_out:
if path == '':
continue
if node.path == path:
keep_node = False
break
if not keep_node:
node.detach()
return root
......@@ -20,7 +20,7 @@ import argparse
import logging
from . import exit_codes
from . import multiplexer
from . import varianter
from . import settings
from . import tree
from .output import BUILTIN_STREAMS, BUILTIN_STREAM_SETS
......@@ -125,7 +125,8 @@ class Parser(object):
dest='subcommand')
# Allow overriding default params by plugins
self.args.mux = multiplexer.Mux(getattr(self.args, "mux-debug", False))
variants = varianter.Varianter(getattr(self.args, "mux-debug", False))
self.args.avocado_variants = variants
# FIXME: Backward compatibility params, to be removed when 36 LTS is
# discontinued
self.args.default_avocado_params = tree.TreeNode()
......
......@@ -176,17 +176,17 @@ class RemoteTestRunner(TestRunner):
return json_result
def run_suite(self, test_suite, mux, timeout=0, replay_map=None):
def run_suite(self, test_suite, variants, timeout=0, replay_map=None):
"""
Run one or more tests and report with test result.
:param params_list: a list of param dicts.
:param mux: A multiplex iterator (unused here)
:param variants: A varianter iterator (unused here)
:return: a set with types of test failures.
"""
del test_suite # using self.job.references instead
del mux # we're not using multiplexation here
del variants # we're not using multiplexation here
if not timeout: # avoid timeout = 0
timeout = None
summary = set()
......
......@@ -468,20 +468,20 @@ class TestRunner(object):
return True
@staticmethod
def _iter_variants(template, mux):
def _iter_variants(template, variants):
"""
Iterate through variants and set the params/variants accordingly.
:param template: test template
:param mux: the Mux object containing the variants
:param variants: the Mux object containing the variants
:return: Yields tuple(test_factory including params, variant id)
:raises ValueError: When variant and template declare params.
"""
for variant, params in mux.itertests():
for variant, params in variants.itertests():
if params:
if "params" in template[1]:
msg = ("Unable to multiplex test %s, params are already "
"present in test factory: %s"
msg = ("Unable to use test variants %s, params are already"
" present in test factory: %s"
% (template[0], template[1]))
raise ValueError(msg)
factory = [template[0], template[1].copy()]
......@@ -490,12 +490,12 @@ class TestRunner(object):
factory = template
yield factory, variant
def run_suite(self, test_suite, mux, timeout=0, replay_map=None):
def run_suite(self, test_suite, variants, timeout=0, replay_map=None):
"""
Run one or more tests and report with test result.
:param test_suite: a list of tests to run.
:param mux: the multiplexer.
:param variants: A varianter iterator to produce test params.
:param timeout: maximum amount of time (in seconds) to execute.
:return: a set with types of test failures.
"""
......@@ -509,7 +509,7 @@ class TestRunner(object):
else:
deadline = None
test_result_total = mux.get_number_of_tests(test_suite)
test_result_total = variants.get_number_of_tests(test_suite)
no_digits = len(str(test_result_total))
self.result.tests_total = test_result_total
self.result.start_tests()
......@@ -520,7 +520,7 @@ class TestRunner(object):
test_template[1]['job'] = self.job
break_loop = False
for test_factory, variant in self._iter_variants(test_template,
mux):
variants):
index += 1
test_parameters = test_factory[1]
name = test_parameters.get("name")
......
......@@ -28,7 +28,7 @@ import time
from . import data_dir
from . import exceptions
from . import multiplexer
from . import varianter
from . import sysinfo
from . import output
from ..utils import asset
......@@ -215,9 +215,9 @@ class Test(unittest.TestCase):
params = []
elif isinstance(params, tuple):
params, mux_path = params[0], params[1]
self.params = multiplexer.AvocadoParams(params, self.name,
mux_path,
self.default_params)
self.params = varianter.AvocadoParams(params, self.name,
mux_path,
self.default_params)
default_timeout = getattr(self, "timeout", None)
self.timeout = self.params.get("timeout", default=default_timeout)
......
......@@ -37,25 +37,10 @@ import collections
import itertools
import locale
import os
import re
from . import output
# Tags to remove node/value
REMOVE_NODE = 0
REMOVE_VALUE = 1
class Control(object): # Few methods pylint: disable=R0903
""" Container used to identify node vs. control sequence """
def __init__(self, code, value=None):
self.code = code
self.value = value
class TreeNode(object):
"""
......@@ -73,8 +58,6 @@ class TreeNode(object):
self.children = []
self._environment = None
self.environment_origin = {}
self.ctrl = []
self.multiplex = None
for child in children:
self.add_child(child)
......@@ -130,28 +113,6 @@ class TreeNode(object):
added as children (recursively they get either appended at the end
or merged into existing node in the previous position.
"""
for ctrl in other.ctrl:
if isinstance(ctrl, Control):
if ctrl.code == REMOVE_NODE:
remove = []
regexp = re.compile(ctrl.value)
for child in self.children:
if regexp.match(child.name):
remove.append(child)
for child in remove:
self.children.remove(child)
elif ctrl.code == REMOVE_VALUE:
remove = []
regexp = re.compile(ctrl.value)
for key in self.value.iterkeys():
if regexp.match(key):
remove.append(key)
for key in remove:
self.value.pop(key, None)
if other.multiplex is True:
self.multiplex = True
elif other.multiplex is False:
self.multiplex = False
self.value.update(other.value)
for child in other.children:
self.add_child(child)
......@@ -292,62 +253,6 @@ class TreeNode(object):
return self
def path_parent(path):
"""
From a given path, return its parent path.
:param path: the node path as string.
:return: the parent path as string.
"""
parent = path.rpartition('/')[0]
if not parent:
return '/'
return parent
def apply_filters(tree, filter_only=None, filter_out=None):
"""
Apply a set of filters to the tree.
The basic filtering is filter only, which includes nodes,
and the filter out rules, that exclude nodes.
Note that filter_out is stronger than filter_only, so if you filter out
something, you could not bypass some nodes by using a filter_only rule.
:param filter_only: the list of paths which will include nodes.
:param filter_out: the list of paths which will exclude nodes.
:return: the original tree minus the nodes filtered by the rules.
"""
if filter_only is None:
filter_only = []
else:
filter_only = [_.rstrip('/') for _ in filter_only if _]
if filter_out is None:
filter_out = []
else:
filter_out = [_.rstrip('/') for _ in filter_out if _]
for node in tree.iter_children_preorder():
keep_node = True
for path in filter_only:
if path == '':
continue
if node.path == path:
keep_node = True
break
if node.parent and node.parent.path == path_parent(path):
keep_node = False
continue
for path in filter_out:
if path == '':
continue
if node.path == path:
keep_node = False
break
if not keep_node:
node.detach()
return tree
#
# Debug version of TreeNode with additional utilities.
#
......
......@@ -365,18 +365,10 @@ class AvocadoParam(object):
yield (leaf.environment_origin[key].path, key, value)
def _report_mux_already_parsed(self, *args, **kwargs):
"""
Raises exception describing that `self.data` alteration is restricted
"""
raise RuntimeError("Mux already parsed, altering is restricted. %s %s"
% (args, kwargs))
class Mux(object):
class Varianter(object):
"""
This is a multiplex object which multiplexes the test_suite.
This object takes care of producing test variants
"""
def __init__(self, debug=False):
......@@ -390,6 +382,7 @@ class Mux(object):
self.debug = debug
self.data = tree.TreeNodeDebug() if debug else tree.TreeNode()
self._mux_path = None
self.ignore_new_data = False # Used to ignore new data when parsed
def parse(self, args):
"""
......@@ -397,22 +390,17 @@ class Mux(object):
:param args: Parsed cmdline arguments
"""
filter_only = getattr(args, 'filter_only', None)
filter_out = getattr(args, 'filter_out', None)
self._parse_basic_injects(args)
mux_tree = tree.apply_filters(self.data, filter_only, filter_out)
self.variants = MuxTree(mux_tree)
self.variants = MuxTree(self.data)
self._mux_path = getattr(args, 'mux_path', None)
if self._mux_path is None:
self._mux_path = ['/run/*']
# disable data alteration (and remove data as they are not useful)
self.data = None
self.data_inject = _report_mux_already_parsed
self.data_merge = _report_mux_already_parsed
def _parse_basic_injects(self, args):
"""
Inject data from the basic injects defined by Mux
Inject data from the basic injects defined by Varianter
:param args: Parsed cmdline arguments
"""
......@@ -422,23 +410,27 @@ class Mux(object):
hasattr(args, "default_avocado_params")):
self.data_merge(args.default_avocado_params)
# Extend default multiplex tree of --mux-inject values
for inject in getattr(args, "mux_inject", []):
entry = inject.split(':', 3)
if len(entry) < 2:
raise ValueError("key:entry pairs required, found only %s"
% (entry))
elif len(entry) == 2: # key, entry
self.data_inject(*entry)
else: # path, key, entry
self.data_inject(key=entry[1], value=entry[2], path=entry[0])
def is_parsed(self):
"""
Reports whether the tree was already multiplexed
"""
return self.variants is not None
def _skip_new_data_check(self, fction, args):
"""
Check whether we can inject the data
:param fction: Name of the data-inject function
:param args: Arguments of the data-inject function
:raise RuntimeError: When data injection is restricted
:return: True if new data should be ignored
"""
if self.is_parsed():
if self.ignore_new_data:
return
raise RuntimeError("Varianter already parsed, unable to execute "
"%s%s" % (fction, args))
def data_inject(self, key, value, path=None): # pylint: disable=E0202
"""
Inject entry to the mux tree (params database)
......@@ -448,6 +440,8 @@ class Mux(object):
:param path: Optional path to the node to which we assign the value,
by default '/'.
"""
if self._skip_new_data_check("data_inject", (key, value, path)):
return
if path:
node = self.data.get_node(path, True)
else:
......@@ -461,11 +455,13 @@ class Mux(object):
:param tree: Tree to be merged into this database.
:type tree: :class:`avocado.core.tree.TreeNode`
"""
if self._skip_new_data_check("data_merge", (tree,)):
return
self.data.merge(tree)
def get_number_of_tests(self, test_suite):
"""
:return: overall number of tests * multiplex variants
:return: overall number of tests * number of variants
"""
# Currently number of tests is symmetrical
if self.variants:
......@@ -480,7 +476,7 @@ class Mux(object):
"""
Yield variant-id and test params
:yield (variant-id, (list of leaves, list of multiplex paths))
:yield (variant-id, (list of leaves, list of default paths))
"""
if self.variants: # Copy template and modify it's params
if self._has_multiple_variants:
......
......@@ -369,10 +369,10 @@ class Diff(CLICmd):
@staticmethod
def _get_variants(resultsdir):
results = []
mux = jobdata.retrieve_mux(resultsdir)
if mux is not None:
variants = jobdata.retrieve_variants(resultsdir)
if variants is not None:
env = set()
for (index, tpl) in enumerate(mux.variants):
for (index, tpl) in enumerate(variants.variants):
paths = ', '.join([x.path for x in tpl])
results.append('Variant %s: %s\n' % (index + 1, paths))
for node in tpl:
......
......@@ -35,12 +35,6 @@ class Multiplex(CLICmd):
def configure(self, parser):
parser = super(Multiplex, self).configure(parser)
parser.add_argument('--filter-only', nargs='*', default=[],
help='Filter only path(s) from multiplexing')
parser.add_argument('--filter-out', nargs='*', default=[],
help='Filter out path(s) from multiplexing')
parser.add_argument('--system-wide', action='store_false',
default=True, dest="mux-skip-defaults",
help="Combine the files with the default "
......@@ -48,9 +42,6 @@ class Multiplex(CLICmd):
parser.add_argument('-c', '--contents', action='store_true',
default=False, help="Shows the node content "
"(variables)")
parser.add_argument('--mux-inject', default=[], nargs='*',
help="Inject [path:]key:node values into "
"the final multiplex tree.")
env_parser = parser.add_argument_group("environment view options")
env_parser.add_argument('-d', '--debug', action='store_true',
dest="mux_debug", default=False,
......@@ -72,11 +63,11 @@ class Multiplex(CLICmd):
if err:
log.error(err)
sys.exit(exit_codes.AVOCADO_FAIL)
mux = args.mux
variants = args.avocado_variants
try:
mux.parse(args)
variants.parse(args)
except (IOError, ValueError) as details:
log.error("Unable to parse mux: %s", details)
log.error("Unable to parse variants: %s", details)
sys.exit(exit_codes.AVOCADO_JOB_FAIL)
if args.tree:
if args.contents:
......@@ -87,11 +78,11 @@ class Multiplex(CLICmd):
verbose += 2
use_utf8 = settings.get_value("runner.output", "utf8",
key_type=bool, default=None)
log.debug(tree.tree_view(mux.variants.root, verbose, use_utf8))
log.debug(tree.tree_view(variants.variants.root, verbose, use_utf8))
sys.exit(exit_codes.AVOCADO_ALL_OK)
log.info('Variants generated:')
for (index, tpl) in enumerate(mux.variants):
for (index, tpl) in enumerate(variants.variants):
if not args.mux_debug:
paths = ', '.join([x.path for x in tpl])
else:
......
......@@ -27,12 +27,6 @@ from avocado.core.settings import settings
from avocado.core.test import ReplaySkipTest
def ignore_call(*args, **kwargs):
"""
Accepts anything and does nothing
"""
class Replay(CLI):
"""
......@@ -64,7 +58,7 @@ class Replay(CLI):
dest='replay_ignore',
type=self._valid_ignore,
default=[],
help='Ignore multiplex (mux) and/or '
help='Ignore variants (variants) and/or '
'configuration (config) from the '
'source job')
......@@ -80,7 +74,7 @@ class Replay(CLI):
return status_list
def _valid_ignore(self, string):
options = ['mux', 'config']
options = ['variants', 'config']
ignore_list = string.split(',')
for item in ignore_list:
if item not in options:
......@@ -126,9 +120,9 @@ class Replay(CLI):
log = logging.getLogger("avocado.app")
err = None
if args.replay_teststatus and 'mux' in args.replay_ignore:
if args.replay_teststatus and 'variants' in args.replay_ignore:
err = ("Option `--replay-test-status` is incompatible with "
"`--replay-ignore mux`.")
"`--replay-ignore variants`.")
elif args.replay_teststatus and args.reference:
err = ("Option --replay-test-status is incompatible with "
"test references given on the command line.")
......@@ -202,25 +196,25 @@ class Replay(CLI):
else:
self.load_config(resultsdir)
if 'mux' in args.replay_ignore:
log.warn("Ignoring multiplex from source job with "
if 'variants' in args.replay_ignore:
log.warn("Ignoring variants from source job with "
"--replay-ignore.")
else:
mux = jobdata.retrieve_mux(resultsdir)
if mux is None:
log.error('Source job multiplex data not found. Aborting.')
variants = jobdata.retrieve_variants(resultsdir)
if variants is None:
log.error('Source job variants data not found. Aborting.')
sys.exit(exit_codes.AVOCADO_JOB_FAIL)
else:
# Ignore data manipulation. This is necessary, because
# we replaced the unparsed object with parsed one. There
# are other plugins running before/after this which might
# want to alter the mux object.
if len(args.mux.data) or args.mux.data.environment:
# want to alter the variants object.
if (len(args.avocado_variants.data) or
args.avocado_variants.data.environment):
log.warning("Using src job Mux data only, use `--replay-"
"ignore mux` to override them.")
setattr(args, "mux", mux)
mux.data_merge = ignore_call
mux.data_inject = ignore_call
"ignore variants` to override them.")
setattr(args, "avocado_variants", variants)
variants.ignore_new_data = True
if args.replay_teststatus:
replay_map = self._create_replay_map(resultsdir,
......
......@@ -132,18 +132,6 @@ class Run(CLICmd):
loader.add_loader_options(parser)
mux = parser.add_argument_group('test parameters')
mux.add_argument('--filter-only', nargs='*', default=[],
help='Filter only path(s) from multiplexing')
mux.add_argument('--filter-out', nargs='*', default=[],
help='Filter out path(s) from multiplexing')
mux.add_argument('--mux-path', nargs='*', default=None,
help="List of paths used to determine path "
"priority when querying for parameters")
mux.add_argument('--mux-inject', default=[], nargs='*',
help="Inject [path:]key:node values into the "
"final multiplex tree.")
filtering = parser.add_argument_group('filtering parameters')
filtering.add_argument('--filter-by-tags', metavar='TAGS',
action='append',
......
......@@ -18,7 +18,7 @@ import os
import re
import sys
from avocado.core import tree, exit_codes
from avocado.core import tree, exit_codes, mux
from avocado.core.plugin_interfaces import CLI
......@@ -37,8 +37,8 @@ else:
# Mapping for yaml flags
YAML_INCLUDE = 100
YAML_USING = 101
YAML_REMOVE_NODE = tree.REMOVE_NODE
YAML_REMOVE_VALUE = tree.REMOVE_VALUE
YAML_REMOVE_NODE = mux.REMOVE_NODE
YAML_REMOVE_VALUE = mux.REMOVE_VALUE
YAML_MUX = 102
__RE_FILE_SPLIT = re.compile(r'(?<!\\):') # split by ':' but not '\\:'
......@@ -59,16 +59,16 @@ class ListOfNodeObjects(list): # Few methods pylint: disable=R0903
pass
def _create_from_yaml(path, cls_node=tree.TreeNode):
def _create_from_yaml(path, cls_node=mux.MuxTreeNode):
""" Create tree structure from yaml stream """
def tree_node_from_values(name, values):
""" Create `name` node and add values """
node = cls_node(str(name))
using = ''
for value in values:
if isinstance(value, tree.TreeNode):
if isinstance(value, cls_node):
node.add_child(value)
elif isinstance(value[0], tree.Control):
elif isinstance(value[0], mux.Control):
if value[0].code == YAML_INCLUDE:
# Include file
ypath = value[1]
......@@ -140,17 +140,17 @@ def _create_from_yaml(path, cls_node=tree.TreeNode):
objects = mapping_to_tree_loader(loader, obj)
else: # This means it's empty node. Don't call mapping_to_tree_loader
objects = ListOfNodeObjects()
objects.append((tree.Control(YAML_MUX), None))
objects.append((mux.Control(YAML_MUX), None))
return objects
Loader.add_constructor(u'!include',
lambda loader, node: tree.Control(YAML_INCLUDE))
lambda loader, node: mux.Control(YAML_INCLUDE))
Loader.add_constructor(u'!using',
lambda loader, node: tree.Control(YAML_USING))
lambda loader, node: mux.Control(YAML_USING))
Loader.add_constructor(u'!remove_node',
lambda loader, node: tree.Control(YAML_REMOVE_NODE))
lambda loader, node: mux.Control(YAML_REMOVE_NODE))
Loader.add_constructor(u'!remove_value',
lambda loader, node: tree.Control(YAML_REMOVE_VALUE))
lambda loader, node: mux.Control(YAML_REMOVE_VALUE))
Loader.add_constructor(u'!mux', mux_loader)
Loader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
mapping_to_tree_loader)
......@@ -200,16 +200,16 @@ def create_from_yaml(paths, debug=False):
def _merge_debug(data, path):
""" Use NamedTreeNodeDebug magic """
node_cls = tree.get_named_tree_cls(path)
node_cls = tree.get_named_tree_cls(path, mux.MuxTreeNodeDebug)
tmp = _create_from_yaml(path, node_cls)
if tmp:
data.merge(tmp)
if not debug:
data = tree.TreeNode()
data = mux.MuxTreeNode()
merge = _merge
else:
data = tree.TreeNodeDebug()
data = mux.MuxTreeNodeDebug()
merge = _merge_debug
path = None
......@@ -249,10 +249,28 @@ class YamlToMux(CLI):
mux.add_argument("-m", "--mux-yaml", nargs='*', metavar="FILE",
help="Location of one or more Avocado"
" multiplex (.yaml) FILE(s) (order dependent)")
mux.add_argument('--mux-filter-only', nargs='*', default=[],
help='Filter only path(s) from multiplexing')
mux.add_argument('--mux-filter-out', nargs='*', default=[],
help='Filter out path(s) from multiplexing')
mux.add_argument('--mux-path', nargs='*', default=None,
help="List of default paths used to determine "
"path priority when querying for parameters")
mux.add_argument('--mux-inject', default=[], nargs='*',
help="Inject [path:]key:node values into the "
"final multiplex tree.")
mux = subparser.add_argument_group("yaml to mux options "
"[deprecated]")
mux.add_argument("--multiplex", nargs='*',
default=None, metavar="FILE",
help="DEPRECATED: Location of one or more Avocado"
" multiplex (.yaml) FILE(s) (order dependent)")
mux.add_argument("--filter-only", nargs='*', default=[],
help="DEPRECATED: Filter only path(s) from "
"multiplexing (use --mux-only instead)")
mux.add_argument("--filter-out", nargs='*', default=[],
help="DEPRECATED: Filter out path(s) from "
"multiplexing (use --mux-out instead)")
@staticmethod
def _log_deprecation_msg(deprecated, current):
......@@ -263,12 +281,34 @@ class YamlToMux(CLI):
logging.getLogger("avocado.app").warning(msg, deprecated, current)
def run(self, args):
# Deprecated filters
only = getattr(args, "filter_only", None)
if only:
self._log_deprecation_msg("--filter-only", "--mux-only")
mux_filter_only = getattr(args, "mux_filter_only")
if mux_filter_only:
args.mux_filter_only = mux_filter_only + only
else:
args.mux_filter_only = only
out = getattr(args, "filter_out", None)
if out:
self._log_deprecation_msg("--filter-out", "--mux-out")
mux_filter_out = getattr(args, "mux_filter_out")
if mux_filter_out:
args.mux_filter_out = mux_filter_out + out
else:
args.mux_filter_out = out
if args.avocado_variants.debug:
data = mux.MuxTreeNodeDebug()
else:
data = mux.MuxTreeNode()
# Merge the multiplex
multiplex_files = getattr(args, "mux_yaml", None)
if multiplex_files:
debug = getattr(args, "mux_debug", False)
try:
args.mux.data_merge(create_from_yaml(multiplex_files, debug))
data.merge(create_from_yaml(multiplex_files, debug))
except IOError as details:
logging.getLogger("avocado.app").error(details.strerror)
sys.exit(exit_codes.AVOCADO_JOB_FAIL)
......@@ -279,7 +319,25 @@ class YamlToMux(CLI):
self._log_deprecation_msg("--multiplex", "--mux-yaml")
debug = getattr(args, "mux_debug", False)
try:
args.mux.data_merge(create_from_yaml(multiplex_files, debug))
data.merge(create_from_yaml(multiplex_files, debug))
from_yaml = create_from_yaml(multiplex_files, debug)
args.avocado_variants.data_merge(from_yaml)
except IOError as details:
logging.getLogger("avocado.app").error(details.strerror)
sys.exit(exit_codes.AVOCADO_JOB_FAIL)
# Extend default multiplex tree of --mux-inject values
for inject in getattr(args, "mux_inject", []):
entry = inject.split(':', 3)
if len(entry) < 2:
raise ValueError("key:entry pairs required, found only %s"
% (entry))
elif len(entry) == 2: # key, entry
entry.insert(0, '') # add path='' (root)
data.get_node(entry[0], True).value[entry[1]] = entry[2]
mux_filter_only = getattr(args, 'mux_filter_only', None)
mux_filter_out = getattr(args, 'mux_filter_out', None)
data = mux.apply_filters(data, mux_filter_only, mux_filter_out)
if data != mux.MuxTreeNode():
args.avocado_variants.data_merge(data)
.. _mux:
.. _variants:
===================
Test variants - Mux
===================
=============
Test Variants
=============
The ``Mux`` is a special mechanism to produce multiple variants of the same
The ``Varianter`` is a special mechanism to produce multiple variants of the same
test with different parameters. This is essential in order to get a decent
coverage and avocado allows several ways to define those parameters from
simple enumeration of key/value pairs to complex trees which allows in simple
......@@ -19,19 +19,19 @@ in reality it follows the way people are used to define dependencies,
therefor it's very simple to use and clear even in complex cases.
The best explanation comes usually from examples, so feel free to scroll down
to `yaml_to_mux plugin`_ section, which uses the default mux plugin to feed
the Mux.
to `yaml_to_mux plugin`_ section, which uses the default variants plugin to feed
the Variants.
Mux internals
-------------
Varianter internals
-------------------
The ``Mux`` is a core part of avocado and one can see it as a ``multiplexed``
The ``Varianter`` is a core part of avocado and one can see it as a ``multiplexed``
database, which contains key/value pairs associated to given paths and
as we are talking about a tree of those, we call the paths ``Nodes``.
Mux allows iterating through all possible combinations which are stored in
the database, which is called ``multiplexation``. Mux yields ``variants``,
Varianter allows iterating through all possible combinations which are stored in
the database, which is called ``multiplexation``. Varianter yields ``variants``,
which are lists of leaf nodes with their values, which are then processed
into ``AvocadoParams``. Those params are available in tests as
``self.params`` and one can query for the current parameters::
......@@ -39,43 +39,43 @@ into ``AvocadoParams``. Those params are available in tests as
self.params.get(key="my_key", path="/some/location/*",
default="default_value")
Let's get back to Mux for a while. As mentioned earlier, it's a database
Let's get back to Variants for a while. As mentioned earlier, it's a database
which allows storing multiple variants of test parameters. To fill the
database, you can use several commands.
1. ``--mux-inject`` - injects directly [path:]key:node values from the
cmdline (see ``avocado multiplex -h``)
2. ``yaml_to_mux plugin`` - allows parsing ``yaml`` files into the Mux
2. ``yaml_to_mux plugin`` - allows parsing ``yaml`` files into the Variants
database (see `yaml_to_mux plugin`_)
3. Custom plugin using the simple ``Mux`` API (see `mux_api`_)
3. Custom plugin using the simple ``Variants`` API (see `variants_api`_)
.. _mux_api:
.. _variants_api:
Mux API
-------
Varianter API
-------------
.. warning:: This API is internal, we might change it at any moment. On the
other hand we maintain ``avocado-virt`` plugin which uses this
API so in such case we'd provide a patch there demonstrating
the necessary changes.
The ``Mux`` object is defined in ``avocado/core/multiplexer.py``, is always
instantiated in ``avocado.core.parser.py`` and always available in
``args.mux``. The basic workflow is:
The ``Varianter`` object is defined in :mod:`avocado.core.varianter`, is always
instantiated in :mod:`avocado.core.parser` and always available in
``args.avocado_variants``. The basic workflow is:
1. Initialize ``Mux`` in ``args.mux``
1. Initialize ``Variants`` in ``args.avocado_variants``
2. Fill it with data (``plugins`` or ``job``)
3. Multiplex it (in ``job``)
4. Iterate through all variants on all job's tests
Once the ``Mux`` object is multiplexed (3), it's restricted to alter the
Once the ``Varianter`` object is multiplexed (3), it's restricted to alter the
data (2) to avoid changing the already produced data.
The main API needed for your plugins, which we are going to try keeping as
stable as possible is:
* mux.is_parsed() - to find out whether the object was already parsed
* is_parsed() - to find out whether the object was already parsed
* data_inject(key, value, path=None) - to inject key/value pairs optionaly
to a given path (by default '/')
* data_merge(tree) - to merge ``avocado.core.tree.TreeNode``-like tree
......@@ -85,7 +85,7 @@ Given these you should be able to implement any kind of parser or params
feeder, should you require one. We favor ``yaml`` and therefor we implemented
a ``yaml_to_mux`` plugin which can be found in
``avocado/plugins/yaml_to_mux.py`` and on it we also describe the way
``Mux`` works: `yaml_to_mux plugin`_
``Varianter`` works: `yaml_to_mux plugin`_
Yaml_to_mux plugin
......@@ -93,7 +93,7 @@ Yaml_to_mux plugin
In order to get a good coverage one always needs to execute the same test
with different parameters or in various environments. Avocado uses the
term ``Multiplexation`` or ``Mux`` to generate multiple variants of the same
term ``Multiplexation`` to generate multiple variants of the same
test with different values. To define these variants and values
`YAML <http://www.yaml.org/>`_ files are used. The benefit of using YAML
file is the visible separation of different scopes. Even very advanced setups
......
......@@ -112,19 +112,19 @@ class ReplayTests(unittest.TestCase):
expected_rc = exit_codes.AVOCADO_FAIL
result = self.run_and_check(cmd_line, expected_rc)
msg = 'Invalid --replay-ignore option. Valid options are ' \
'(more than one allowed): mux,config'
'(more than one allowed): variants,config'
self.assertIn(msg, result.stderr)
def test_run_replay_ignoremux(self):
def test_run_replay_ignorevariants(self):
"""
Runs a replay job ignoring the mux.
Runs a replay job ignoring the variants.
"""
cmd_line = ('./scripts/avocado run --replay %s --replay-ignore mux '
cmd_line = ('./scripts/avocado run --replay %s --replay-ignore variants '
'--job-results-dir %s --sysinfo=off'
% (self.jobid, self.tmpdir))
expected_rc = exit_codes.AVOCADO_ALL_OK
result = self.run_and_check(cmd_line, expected_rc)
msg = 'Ignoring multiplex from source job with --replay-ignore.'
msg = 'Ignoring variants from source job with --replay-ignore.'
self.assertIn(msg, result.stderr)
def test_run_replay_invalidstatus(self):
......@@ -164,17 +164,17 @@ class ReplayTests(unittest.TestCase):
msg = "Currently we don't replay jobs in remote hosts."
self.assertIn(msg, result.stderr)
def test_run_replay_status_and_mux(self):
def test_run_replay_status_and_variants(self):
"""
Runs a replay job with custom a mux and using '--replay-test-status'
Runs a replay job with custom variants using '--replay-test-status'
"""
cmd_line = ('./scripts/avocado run --replay %s --replay-ignore mux '
cmd_line = ('./scripts/avocado run --replay %s --replay-ignore variants '
'--replay-test-status FAIL --job-results-dir %s '
'--sysinfo=off' % (self.jobid, self.tmpdir))
expected_rc = exit_codes.AVOCADO_FAIL
result = self.run_and_check(cmd_line, expected_rc)
msg = ("Option `--replay-test-status` is incompatible with "
"`--replay-ignore mux`")
"`--replay-ignore variants`")
self.assertIn(msg, result.stderr)
def test_run_replay_status_and_references(self):
......
import copy
import itertools
import pickle
import sys
from avocado.core import multiplexer
from avocado.core import tree
from avocado.core import mux, tree, varianter
from avocado.plugins import yaml_to_mux
if sys.version_info[:2] == (2, 6):
import unittest2 as unittest
else:
......@@ -23,6 +24,190 @@ def combine(leaves_pools):
return itertools.product(*leaves_pools[1])
class TestMuxTree(unittest.TestCase):
# Share tree with all tests
tree = yaml_to_mux.create_from_yaml(['/:' + PATH_PREFIX +
'examples/mux-selftest.yaml'])
def test_node_order(self):
self.assertIsInstance(self.tree, mux.MuxTreeNode)
self.assertEqual('hw', self.tree.children[0])
self.assertEqual({'cpu_CFLAGS': '-march=core2'},
self.tree.children[0].children[0].children[0].value)
disk = self.tree.children[0].children[1]
self.assertEqual('scsi', disk.children[0])
self.assertEqual({'disk_type': 'scsi', 'corruptlist': ['againlist']},
disk.children[0].value)
self.assertEqual('virtio', disk.children[1])
self.assertEqual({}, disk.children[1].value)
self.assertEqual('distro', self.tree.children[1])
self.assertEqual('env', self.tree.children[2])
self.assertEqual({'opt_CFLAGS': '-O2'},
self.tree.children[2].children[0].value)
def test_eq(self):
# Copy
tree2 = copy.deepcopy(self.tree)
self.assertEqual(self.tree, tree2)
# Additional node
child = mux.MuxTreeNode("20", {'name': 'Heisenbug'})
tree2.children[1].children[1].add_child(child)
self.assertNotEqual(self.tree, tree2)
# Should match again
child.detach()
self.assertEqual(self.tree, tree2)
# Missing node
tree2.children[1].children[1].detach()
self.assertNotEqual(self.tree, tree2)
self.assertEqual(self.tree.children[0], tree2.children[0])
# Different value
tree2.children[0].children[0].children[0].value = {'something': 'else'}
self.assertNotEqual(self.tree.children[0], tree2.children[0])
tree3 = mux.MuxTreeNode()
self.assertNotEqual(tree3, tree2)
# Merge
tree3.merge(tree2)
self.assertEqual(tree3, tree2)
# Add_child existing
tree3.add_child(tree2.children[0])
self.assertEqual(tree3, tree2)
def test_links(self):
""" Verify child->parent links """
for leaf in self.tree:
self.assertEqual(leaf.root, self.tree)
def test_basic_functions(self):
# repr
self.assertEqual("MuxTreeNode(name='hw')", repr(self.tree.children[0]))
# str
self.assertEqual("/distro/mint: init=systemv",
str(self.tree.children[1].children[1]))
# len
self.assertEqual(8, len(self.tree)) # number of leaves
# __iter__
self.assertEqual(8, sum((1 for _ in self.tree))) # number of leaves
# .root
self.assertEqual(id(self.tree),
id(self.tree.children[0].children[0].children[0].root)
)
# .parents
self.assertEqual(['hw', ''], self.tree.children[0].children[0].parents)
# environment / (root)
self.assertEqual({}, self.tree.environment)
# environment /hw (nodes first)
self.assertEqual({'corruptlist': ['upper_node_list']},
self.tree.children[0].environment)
cpu = self.tree.children[0].children[0]
# environment /hw/cpu (mixed env)
self.assertEqual({'corruptlist': ['upper_node_list'],
'joinlist': ['first_item']},
cpu.environment)
# environment /hw/cpu/amd (list extension)
vals = {'corruptlist': ['upper_node_list'],
'cpu_CFLAGS': '-march=athlon64',
'joinlist': ['first_item', 'second', 'third']}
self.assertEqual(vals, cpu.children[1].environment)
# environment /hw/cpu/arm (deep env)
vals = {'corruptlist': ['upper_node_list'], 'joinlist': ['first_item'],
'cpu_CFLAGS': '-mabi=apcs-gnu '
'-march=armv8-a -mtune=arm8'}
self.assertEqual(vals, cpu.children[2].environment)
# environment /hw/disk (list -> string)
vals = {'corruptlist': 'nonlist', 'disk_type': 'virtio'}
disk = self.tree.children[0].children[1]
self.assertEqual(vals, disk.environment)
# environment /hw/disk/scsi (string -> list)
vals = {'corruptlist': ['againlist'], 'disk_type': 'scsi'}
self.assertEqual(vals, disk.children[0].environment)
# environment /env
vals = {'opt_CFLAGS': '-Os'}
self.assertEqual(vals, self.tree.children[2].environment)
# leaves order
leaves = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', 'mint',
'prod']
self.assertEqual(leaves, self.tree.get_leaves())
# ascii contain all leaves and doesn't raise any exceptions
ascii = tree.tree_view(self.tree, 0, False)
for leaf in leaves:
self.assertIn(leaf, ascii, "Leaf %s not in asci:\n%s"
% (leaf, ascii))
def test_filters(self):
tree2 = copy.deepcopy(self.tree)
exp = ['intel', 'amd', 'arm', 'fedora', 'mint', 'prod']
act = mux.apply_filters(tree2,
filter_only=['/hw/cpu', '']).get_leaves()
self.assertEqual(exp, act)
tree2 = copy.deepcopy(self.tree)
exp = ['scsi', 'virtio', 'fedora', 'mint', 'prod']
act = mux.apply_filters(tree2,
filter_out=['/hw/cpu', '']).get_leaves()
self.assertEqual(exp, act)
def test_merge_trees(self):
tree2 = copy.deepcopy(self.tree)
tree3 = mux.MuxTreeNode()
tree3.add_child(mux.MuxTreeNode('hw', {'another_value': 'bbb'}))
tree3.children[0].add_child(mux.MuxTreeNode('nic'))
tree3.children[0].children[0].add_child(mux.MuxTreeNode('default'))
tree3.children[0].children[0].add_child(mux.MuxTreeNode('virtio', {'nic': 'virtio'}))
tree3.children[0].add_child(mux.MuxTreeNode('cpu', {'test_value': ['z']}))
tree2.merge(tree3)
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'default', 'virtio',
'fedora', 'mint', 'prod']
self.assertEqual(exp, tree2.get_leaves())
self.assertEqual({'corruptlist': ['upper_node_list'],
'another_value': 'bbb'},
tree2.children[0].value)
self.assertEqual({'joinlist': ['first_item'], 'test_value': ['z']},
tree2.children[0].children[0].value)
self.assertFalse(tree2.children[0].children[2].children[0].value)
self.assertEqual({'nic': 'virtio'},
tree2.children[0].children[2].children[1].value)
def test_advanced_yaml(self):
tree2 = yaml_to_mux.create_from_yaml(['/:' + PATH_PREFIX +
'examples/mux-selftest-advanced.'
'yaml'])
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', '6',
'7', 'gentoo', 'mint', 'prod', 'new_node', 'on']
act = tree2.get_leaves()
oldroot = tree2.children[0]
self.assertEqual(exp, act)
self.assertEqual(tree2.children[0].children[0].path, "/virt/hw")
self.assertEqual({'enterprise': True},
oldroot.children[1].children[1].value)
self.assertEqual({'new_init': 'systemd'},
oldroot.children[1].children[0].value)
self.assertEqual({'is_cool': True},
oldroot.children[1].children[2].value)
self.assertEqual({'new_value': 'something'},
oldroot.children[3].children[0].children[0].value)
# Convert values, but not keys
self.assertEqual({'on': True, "true": "true"},
oldroot.children[4].value)
# multiplex root (always True)
self.assertEqual(tree2.multiplex, None)
# multiplex /virt/
self.assertEqual(tree2.children[0].multiplex, None)
# multiplex /virt/hw
self.assertEqual(tree2.children[0].children[0].multiplex, None)
# multiplex /virt/distro
self.assertEqual(tree2.children[0].children[1].multiplex, True)
# multiplex /virt/env
self.assertEqual(tree2.children[0].children[2].multiplex, True)
# multiplex /virt/absolutely
self.assertEqual(tree2.children[0].children[3].multiplex, None)
# multiplex /virt/distro/fedora
self.assertEqual(tree2.children[0].children[1].children[0].multiplex,
None)
def test_get_node(self):
self.assertRaises(ValueError,
self.tree.get_node, '/non-existing-node')
class TestMultiplex(unittest.TestCase):
@unittest.skipIf(not yaml_to_mux.MULTIPLEX_CAPABLE,
......@@ -31,16 +216,16 @@ class TestMultiplex(unittest.TestCase):
self.mux_tree = yaml_to_mux.create_from_yaml(['/:' + PATH_PREFIX +
'examples/mux-selftest.'
'yaml'])
self.mux_full = tuple(multiplexer.MuxTree(self.mux_tree))
self.mux_full = tuple(varianter.MuxTree(self.mux_tree))
def test_empty(self):
act = tuple(multiplexer.MuxTree(tree.TreeNode()))
act = tuple(varianter.MuxTree(mux.MuxTreeNode()))
self.assertEqual(act, (['', ],))
def test_partial(self):
exp = (['intel', 'scsi'], ['intel', 'virtio'], ['amd', 'scsi'],
['amd', 'virtio'], ['arm', 'scsi'], ['arm', 'virtio'])
act = tuple(multiplexer.MuxTree(self.mux_tree.children[0]))
act = tuple(varianter.MuxTree(self.mux_tree.children[0]))
self.assertEqual(act, exp)
def test_full(self):
......@@ -49,7 +234,7 @@ class TestMultiplex(unittest.TestCase):
def test_create_variants(self):
from_file = yaml_to_mux.create_from_yaml(
["/:" + PATH_PREFIX + 'examples/mux-selftest.yaml'])
from_file = multiplexer.MuxTree(from_file)
from_file = varianter.MuxTree(from_file)
self.assertEqual(self.mux_full, tuple(from_file))
# Filters are tested in tree_unittests, only verify `multiplex_yamls` calls
......@@ -57,17 +242,17 @@ class TestMultiplex(unittest.TestCase):
exp = (['intel', 'scsi'], ['intel', 'virtio'])
act = yaml_to_mux.create_from_yaml(["/:" + PATH_PREFIX +
'examples/mux-selftest.yaml'])
act = tree.apply_filters(act, ('/hw/cpu/intel', '/distro/fedora',
'/hw'))
act = tuple(multiplexer.MuxTree(act))
act = mux.apply_filters(act, ('/hw/cpu/intel', '/distro/fedora',
'/hw'))
act = tuple(varianter.MuxTree(act))
self.assertEqual(act, exp)
def test_filter_out(self):
act = yaml_to_mux.create_from_yaml(["/:" + PATH_PREFIX +
'examples/mux-selftest.yaml'])
act = tree.apply_filters(act, None, ('/hw/cpu/intel', '/distro/fedora',
'/distro'))
act = tuple(multiplexer.MuxTree(act))
act = mux.apply_filters(act, None, ('/hw/cpu/intel', '/distro/fedora',
'/distro'))
act = tuple(varianter.MuxTree(act))
self.assertEqual(len(act), 4)
self.assertEqual(len(act[0]), 3)
str_act = str(act)
......@@ -82,13 +267,13 @@ class TestAvocadoParams(unittest.TestCase):
def setUp(self):
yamls = yaml_to_mux.create_from_yaml(["/:" + PATH_PREFIX +
'examples/mux-selftest-params.yaml'])
self.yamls = iter(multiplexer.MuxTree(yamls))
self.params1 = multiplexer.AvocadoParams(self.yamls.next(), 'Unittest1',
['/ch0/*', '/ch1/*'], {})
self.yamls = iter(varianter.MuxTree(yamls))
self.params1 = varianter.AvocadoParams(self.yamls.next(), 'Unittest1',
['/ch0/*', '/ch1/*'], {})
self.yamls.next() # Skip 2nd
self.yamls.next() # and 3rd
self.params2 = multiplexer.AvocadoParams(self.yamls.next(), 'Unittest2',
['/ch1/*', '/ch0/*'], {})
self.params2 = varianter.AvocadoParams(self.yamls.next(), 'Unittest2',
['/ch1/*', '/ch0/*'], {})
@unittest.skipIf(not yaml_to_mux.MULTIPLEX_CAPABLE, "Not multiplex capable")
def test_pickle(self):
......@@ -102,7 +287,7 @@ class TestAvocadoParams(unittest.TestCase):
self.assertNotEqual(self.params1, self.params2)
repr(self.params1)
str(self.params1)
str(multiplexer.AvocadoParams([], 'Unittest', [], {}))
str(varianter.AvocadoParams([], 'Unittest', [], {}))
self.assertEqual(15, sum([1 for _ in self.params1.iteritems()]))
@unittest.skipIf(not yaml_to_mux.MULTIPLEX_CAPABLE, "Not multiplex capable")
......@@ -201,5 +386,20 @@ class TestAvocadoParams(unittest.TestCase):
'also equal')
class TestPathParent(unittest.TestCase):
def test_empty_string(self):
self.assertEqual(mux.path_parent(''), '/')
def test_on_root(self):
self.assertEqual(mux.path_parent('/'), '/')
def test_direct_parent(self):
self.assertEqual(mux.path_parent('/os/linux'), '/os')
def test_false_direct_parent(self):
self.assertNotEqual(mux.path_parent('/os/linux'), '/')
if __name__ == '__main__':
unittest.main()
import copy
import sys
if sys.version_info[:2] == (2, 6):
import unittest2 as unittest
else:
import unittest
from avocado.core import tree
from avocado.plugins import yaml_to_mux
if __name__ == "__main__":
PATH_PREFIX = "../../../../"
else:
PATH_PREFIX = ""
class TestTree(unittest.TestCase):
# Share tree with all tests
tree = yaml_to_mux.create_from_yaml(['/:' + PATH_PREFIX +
'examples/mux-selftest.yaml'])
def test_node_order(self):
self.assertIsInstance(self.tree, tree.TreeNode)
self.assertEqual('hw', self.tree.children[0])
self.assertEqual({'cpu_CFLAGS': '-march=core2'},
self.tree.children[0].children[0].children[0].value)
disk = self.tree.children[0].children[1]
self.assertEqual('scsi', disk.children[0])
self.assertEqual({'disk_type': 'scsi', 'corruptlist': ['againlist']},
disk.children[0].value)
self.assertEqual('virtio', disk.children[1])
self.assertEqual({}, disk.children[1].value)
self.assertEqual('distro', self.tree.children[1])
self.assertEqual('env', self.tree.children[2])
self.assertEqual({'opt_CFLAGS': '-O2'},
self.tree.children[2].children[0].value)
def test_eq(self):
# Copy
tree2 = copy.deepcopy(self.tree)
self.assertEqual(self.tree, tree2)
# Additional node
child = tree.TreeNode("20", {'name': 'Heisenbug'})
tree2.children[1].children[1].add_child(child)
self.assertNotEqual(self.tree, tree2)
# Should match again
child.detach()
self.assertEqual(self.tree, tree2)
# Missing node
tree2.children[1].children[1].detach()
self.assertNotEqual(self.tree, tree2)
self.assertEqual(self.tree.children[0], tree2.children[0])
# Different value
tree2.children[0].children[0].children[0].value = {'something': 'else'}
self.assertNotEqual(self.tree.children[0], tree2.children[0])
tree3 = tree.TreeNode()
self.assertNotEqual(tree3, tree2)
# Merge
tree3.merge(tree2)
self.assertEqual(tree3, tree2)
# Add_child existing
tree3.add_child(tree2.children[0])
self.assertEqual(tree3, tree2)
def test_links(self):
""" Verify child->parent links """
for leaf in self.tree:
self.assertEqual(leaf.root, self.tree)
def test_basic_functions(self):
# repr
self.assertEqual("TreeNode(name='hw')", repr(self.tree.children[0]))
# str
self.assertEqual("/distro/mint: init=systemv",
str(self.tree.children[1].children[1]))
# len
self.assertEqual(8, len(self.tree)) # number of leaves
# __iter__
self.assertEqual(8, sum((1 for _ in self.tree))) # number of leaves
# .root
self.assertEqual(id(self.tree),
id(self.tree.children[0].children[0].children[0].root)
)
# .parents
self.assertEqual(['hw', ''], self.tree.children[0].children[0].parents)
# environment / (root)
self.assertEqual({}, self.tree.environment)
# environment /hw (nodes first)
self.assertEqual({'corruptlist': ['upper_node_list']},
self.tree.children[0].environment)
cpu = self.tree.children[0].children[0]
# environment /hw/cpu (mixed env)
self.assertEqual({'corruptlist': ['upper_node_list'],
'joinlist': ['first_item']},
cpu.environment)
# environment /hw/cpu/amd (list extension)
vals = {'corruptlist': ['upper_node_list'],
'cpu_CFLAGS': '-march=athlon64',
'joinlist': ['first_item', 'second', 'third']}
self.assertEqual(vals, cpu.children[1].environment)
# environment /hw/cpu/arm (deep env)
vals = {'corruptlist': ['upper_node_list'], 'joinlist': ['first_item'],
'cpu_CFLAGS': '-mabi=apcs-gnu '
'-march=armv8-a -mtune=arm8'}
self.assertEqual(vals, cpu.children[2].environment)
# environment /hw/disk (list -> string)
vals = {'corruptlist': 'nonlist', 'disk_type': 'virtio'}
disk = self.tree.children[0].children[1]
self.assertEqual(vals, disk.environment)
# environment /hw/disk/scsi (string -> list)
vals = {'corruptlist': ['againlist'], 'disk_type': 'scsi'}
self.assertEqual(vals, disk.children[0].environment)
# environment /env
vals = {'opt_CFLAGS': '-Os'}
self.assertEqual(vals, self.tree.children[2].environment)
# leaves order
leaves = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', 'mint',
'prod']
self.assertEqual(leaves, self.tree.get_leaves())
# ascii contain all leaves and doesn't raise any exceptions
ascii = tree.tree_view(self.tree, 0, False)
for leaf in leaves:
self.assertIn(leaf, ascii, "Leaf %s not in asci:\n%s"
% (leaf, ascii))
def test_filters(self):
tree2 = copy.deepcopy(self.tree)
exp = ['intel', 'amd', 'arm', 'fedora', 'mint', 'prod']
act = tree.apply_filters(tree2,
filter_only=['/hw/cpu', '']).get_leaves()
self.assertEqual(exp, act)
tree2 = copy.deepcopy(self.tree)
exp = ['scsi', 'virtio', 'fedora', 'mint', 'prod']
act = tree.apply_filters(tree2,
filter_out=['/hw/cpu', '']).get_leaves()
self.assertEqual(exp, act)
def test_merge_trees(self):
tree2 = copy.deepcopy(self.tree)
tree3 = tree.TreeNode()
tree3.add_child(tree.TreeNode('hw', {'another_value': 'bbb'}))
tree3.children[0].add_child(tree.TreeNode('nic'))
tree3.children[0].children[0].add_child(tree.TreeNode('default'))
tree3.children[0].children[0].add_child(tree.TreeNode('virtio',
{'nic': 'virtio'}
))
tree3.children[0].add_child(tree.TreeNode('cpu',
{'test_value': ['z']}))
tree2.merge(tree3)
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'default', 'virtio',
'fedora', 'mint', 'prod']
self.assertEqual(exp, tree2.get_leaves())
self.assertEqual({'corruptlist': ['upper_node_list'],
'another_value': 'bbb'},
tree2.children[0].value)
self.assertEqual({'joinlist': ['first_item'], 'test_value': ['z']},
tree2.children[0].children[0].value)
self.assertFalse(tree2.children[0].children[2].children[0].value)
self.assertEqual({'nic': 'virtio'},
tree2.children[0].children[2].children[1].value)
def test_advanced_yaml(self):
tree2 = yaml_to_mux.create_from_yaml(['/:' + PATH_PREFIX +
'examples/mux-selftest-advanced.'
'yaml'])
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', '6',
'7', 'gentoo', 'mint', 'prod', 'new_node', 'on']
act = tree2.get_leaves()
oldroot = tree2.children[0]
self.assertEqual(exp, act)
self.assertEqual(tree2.children[0].children[0].path, "/virt/hw")
self.assertEqual({'enterprise': True},
oldroot.children[1].children[1].value)
self.assertEqual({'new_init': 'systemd'},
oldroot.children[1].children[0].value)
self.assertEqual({'is_cool': True},
oldroot.children[1].children[2].value)
self.assertEqual({'new_value': 'something'},
oldroot.children[3].children[0].children[0].value)
# Convert values, but not keys
self.assertEqual({'on': True, "true": "true"},
oldroot.children[4].value)
# multiplex root (always True)
self.assertEqual(tree2.multiplex, None)
# multiplex /virt/
self.assertEqual(tree2.children[0].multiplex, None)
# multiplex /virt/hw
self.assertEqual(tree2.children[0].children[0].multiplex, None)
# multiplex /virt/distro
self.assertEqual(tree2.children[0].children[1].multiplex, True)
# multiplex /virt/env
self.assertEqual(tree2.children[0].children[2].multiplex, True)
# multiplex /virt/absolutely
self.assertEqual(tree2.children[0].children[3].multiplex, None)
# multiplex /virt/distro/fedora
self.assertEqual(tree2.children[0].children[1].children[0].multiplex,
None)
def test_get_node(self):
self.assertRaises(ValueError,
self.tree.get_node, '/non-existing-node')
class TestPathParent(unittest.TestCase):
def test_empty_string(self):
self.assertEqual(tree.path_parent(''), '/')
def test_on_root(self):
self.assertEqual(tree.path_parent('/'), '/')
def test_direct_parent(self):
self.assertEqual(tree.path_parent('/os/linux'), '/os')
def test_false_direct_parent(self):
self.assertNotEqual(tree.path_parent('/os/linux'), '/')
if __name__ == '__main__':
unittest.main()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册