提交 e1363982 编写于 作者: L Lukáš Doktor

avocado.multiplexer: Support for debug run

This complex patchset adds debug version of yaml parser. It stores
the origin of each value from environment. For list it stores
per-slice origin too.

Debug mode is NOT suitable for run-ner, only for multiplex-er. It
corrupts the values by adding the origin after the value.

The unittests were adjusted and enhanced. I added explanation into the
examples/mux-selftests.yaml describing what the weird names stands for
and what they test. I added couple of check which stress the old tests
and couple of them test the new features.
Signed-off-by: NLukáš Doktor <ldoktor@redhat.com>
上级 5d96e0c9
...@@ -94,7 +94,7 @@ class TreeNode(object): ...@@ -94,7 +94,7 @@ class TreeNode(object):
Append node as child. Nodes with the same name gets merged into the Append node as child. Nodes with the same name gets merged into the
existing position. existing position.
""" """
if isinstance(node, self.__class__): if isinstance(node, TreeNode):
if node.name in self.children: if node.name in self.children:
self.children[self.children.index(node.name)].merge(node) self.children[self.children.index(node.name)].merge(node)
else: else:
...@@ -288,13 +288,14 @@ class TreeNode(object): ...@@ -288,13 +288,14 @@ class TreeNode(object):
return self return self
def _create_from_yaml(stream): class Value(tuple): # Few methods pylint: disable=R0903
""" Create tree structure from yaml stream """
class Value(tuple): """ Used to mark values to simplify checking for node vs. value """
pass
""" Used to mark values to simplify checking for node vs. value """
pass
def _create_from_yaml(path, cls_node=TreeNode):
""" Create tree structure from yaml stream """
def tree_node_from_values(name, values): def tree_node_from_values(name, values):
""" Create `name` node and add values """ """ Create `name` node and add values """
node_children = [] node_children = []
...@@ -304,10 +305,12 @@ def _create_from_yaml(stream): ...@@ -304,10 +305,12 @@ def _create_from_yaml(stream):
node_children.append(value) node_children.append(value)
else: else:
node_values.append(value) node_values.append(value)
return TreeNode(name, dict(node_values), children=node_children) return cls_node(name, dict(node_values), children=node_children)
def mapping_to_tree_loader(loader, node): def mapping_to_tree_loader(loader, node):
""" Maps yaml mapping tag to TreeNode structure """
def is_node(values): def is_node(values):
""" Whether these values represent node or just random values """
if (isinstance(values, list) and values if (isinstance(values, list) and values
and isinstance(values[0], (Value, TreeNode))): and isinstance(values[0], (Value, TreeNode))):
# When any value is TreeNode or Value, all of them are already # When any value is TreeNode or Value, all of them are already
...@@ -320,26 +323,44 @@ def _create_from_yaml(stream): ...@@ -320,26 +323,44 @@ def _create_from_yaml(stream):
if is_node(values): # New node if is_node(values): # New node
objects.append(tree_node_from_values(name, values)) objects.append(tree_node_from_values(name, values))
elif values is None: # Empty node elif values is None: # Empty node
objects.append(TreeNode(name)) objects.append(cls_node(name))
else: # Values else: # Values
objects.append(Value((name, values))) objects.append(Value((name, values)))
return objects return objects
Loader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, Loader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
mapping_to_tree_loader) mapping_to_tree_loader)
return tree_node_from_values('', yaml.load(stream, Loader))
with open(path) as stream:
return tree_node_from_values('', yaml.load(stream, Loader))
def create_from_yaml(paths):
def create_from_yaml(paths, debug=False):
""" """
Create tree structure from yaml-like file Create tree structure from yaml-like file
:param fileobj: File object to be processed :param fileobj: File object to be processed
:raise SyntaxError: When yaml-file is corrupted :raise SyntaxError: When yaml-file is corrupted
:return: Root of the created tree structure :return: Root of the created tree structure
""" """
data = TreeNode() def _merge(data, path):
""" Normal run """
data.merge(_create_from_yaml(path))
def _merge_debug(data, path):
""" Use NamedTreeNodeDebug magic """
node_cls = tree_debug.get_named_tree_cls(path)
data.merge(_create_from_yaml(path, node_cls))
if not debug:
data = TreeNode()
merge = _merge
else:
from avocado.core import tree_debug
data = tree_debug.TreeNodeDebug()
merge = _merge_debug
try: try:
for path in paths: for path in paths:
data.merge(_create_from_yaml(open(path).read())) merge(data, path)
except (yaml.scanner.ScannerError, yaml.parser.ParserError) as err: except (yaml.scanner.ScannerError, yaml.parser.ParserError) as err:
raise SyntaxError(err) raise SyntaxError(err)
return data return data
......
"""
Debug version of the avocado.core.tree.TreeNode with additional utils.
:license: GPLv2
:copyright: Red Hat, Inc. 2014
:author: Lukas Doktor <ldoktor@redhat.com>
"""
import itertools
import os
from avocado.core import output
from avocado.core.tree import TreeNode
class OutputValue(object): # only container pylint: disable=R0903
""" Ordinary value with some debug info """
def __init__(self, value, node, srcyaml):
self.value = value
self.node = node
self.yaml = srcyaml
def __str__(self):
return "%s%s@%s:%s%s" % (self.value,
output.term_support.LOWLIGHT,
self.yaml, self.node.path,
output.term_support.ENDC)
class OutputList(list): # only container pylint: disable=R0903
""" List with some debug info """
def __init__(self, values, nodes, yamls):
super(OutputList, self).__init__(values)
self.nodes = nodes
self.yamls = yamls
def __add__(self, other):
""" Keep attrs separate in order to print the origins """
value = super(OutputList, self).__add__(other)
return OutputList(value,
self.nodes + other.nodes,
self.yamls + other.yamls)
def __str__(self):
color = output.term_support.LOWLIGHT
cend = output.term_support.ENDC
return ' + '.join("%s%s@%s:%s%s"
% (_[0], color, _[1], _[2].path, cend)
for _ in itertools.izip(self, self.yamls,
self.nodes))
class ValueDict(dict): # only container pylint: disable=R0903
""" Dict which stores the origin of the items """
def __init__(self, srcyaml, node, values):
super(ValueDict, self).__init__()
self.yaml = srcyaml
self.node = node
self.yaml_per_key = {}
for key, value in values.iteritems():
self[key] = value
def __setitem__(self, key, value):
""" Store yaml_per_key and value """
# Merge is responsible to set `self.yaml` to current file
self.yaml_per_key[key] = self.yaml
return super(ValueDict, self).__setitem__(key, value)
def __getitem__(self, key):
"""
This is debug run. Fake the results and return either
OutputValue (let's call it string) and OutputList. These
overrides the `__str__` and return string with origin.
:warning: Returned values are unusable in tests!
"""
value = super(ValueDict, self).__getitem__(key)
origin = self.yaml_per_key.get(key)
if isinstance(value, list):
value = OutputList([value], [self.node], [origin])
else:
value = OutputValue(value, self.node, origin)
return value
def iteritems(self):
""" Slower implementation with the use of __getitem__ """
for key in self.iterkeys():
yield key, self[key]
raise StopIteration
class TreeNodeDebug(TreeNode): # only container pylint: disable=R0903
"""
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):
if value is None:
value = {}
if srcyaml:
srcyaml = os.path.relpath(srcyaml)
super(TreeNodeDebug, self).__init__(name,
ValueDict(srcyaml, self, value),
parent, children)
self.yaml = srcyaml
def merge(self, other):
"""
Override origin with the one from other tree. Updated/Newly set values
are going to use this location as origin.
"""
if hasattr(other, 'yaml'):
srcyaml = os.path.relpath(other.yaml)
# when we use TreeNodeDebug, value is always ValueDict
self.value.yaml_per_key.update(other.value.yaml_per_key) # pylint: disable=E1101
else:
srcyaml = "Unknown"
self.yaml = srcyaml
self.value.yaml = srcyaml
return super(TreeNodeDebug, self).merge(other)
def get_named_tree_cls(path):
""" Return TreeNodeDebug class with hardcoded yaml path """
class NamedTreeNodeDebug(TreeNodeDebug): # pylint: disable=R0903
""" Fake class with hardcoded yaml path """
def __init__(self, name='', value=None, parent=None,
children=None):
super(NamedTreeNodeDebug, self).__init__(name, value, parent,
children, path)
return NamedTreeNodeDebug
...@@ -77,12 +77,13 @@ def multiplex(*args): ...@@ -77,12 +77,13 @@ def multiplex(*args):
yield tuple(prod) yield tuple(prod)
def multiplex_yamls(input_yamls, filter_only=None, filter_out=None): def multiplex_yamls(input_yamls, filter_only=None, filter_out=None,
debug=False):
if filter_only is None: if filter_only is None:
filter_only = [] filter_only = []
if filter_out is None: if filter_out is None:
filter_out = [] filter_out = []
input_tree = tree.create_from_yaml(input_yamls) input_tree = tree.create_from_yaml(input_yamls, debug)
final_tree = tree.apply_filters(input_tree, filter_only, filter_out) final_tree = tree.apply_filters(input_tree, filter_only, filter_out)
leaves = (x for x in final_tree.iter_leaves() if x.parent is not None) leaves = (x for x in final_tree.iter_leaves() if x.parent is not None)
variants = multiplex(leaves) variants = multiplex(leaves)
......
...@@ -50,6 +50,9 @@ class Multiplexer(plugin.Plugin): ...@@ -50,6 +50,9 @@ class Multiplexer(plugin.Plugin):
self.parser.add_argument('-c', '--contents', action='store_true', default=False, self.parser.add_argument('-c', '--contents', action='store_true', default=False,
help="Shows the variant's content (variables)") help="Shows the variant's content (variables)")
self.parser.add_argument('-d', '--debug', action='store_true',
default=False, help="Debug multiplexed "
"files.")
super(Multiplexer, self).configure(self.parser) super(Multiplexer, self).configure(self.parser)
def run(self, args): def run(self, args):
...@@ -71,12 +74,19 @@ class Multiplexer(plugin.Plugin): ...@@ -71,12 +74,19 @@ class Multiplexer(plugin.Plugin):
variants = multiplexer.multiplex_yamls(multiplex_files, variants = multiplexer.multiplex_yamls(multiplex_files,
args.filter_only, args.filter_only,
args.filter_out) args.filter_out,
args.debug)
view.notify(event='message', msg='Variants generated:') view.notify(event='message', msg='Variants generated:')
for (index, tpl) in enumerate(variants): for (index, tpl) in enumerate(variants):
paths = ', '.join([x.path for x in tpl]) if not args.debug:
view.notify(event='minor', msg='Variant %s: %s' % paths = ', '.join([x.path for x in tpl])
else:
color = output.term_support.LOWLIGHT
cend = output.term_support.ENDC
paths = ', '.join(["%s%s@%s%s" % (_.name, color, _.yaml, cend)
for _ in tpl])
view.notify(event='minor', msg='\nVariant %s: %s' %
(index + 1, paths)) (index + 1, paths))
if args.contents: if args.contents:
env = {} env = {}
......
# Special values
# joinlist: list which gets combined while getting environment
# corruptlist: list which is overwritten with string and again by list
# /distro: should be merged from two separated trees into the position of
# the first one. PS: Don't do this in production, it works but
# it's not nice and readable... here it simulates the use of
# multiple files and checks that the node ordering works fine.
# /env/opt_CFLAGS: Should be present in merged node
# /env/prod/opt_CFLAGS: value should be overridden by latter node
hw: hw:
cpu: cpu:
test_value: joinlist:
- a - first_item
intel: intel:
cpu_CFLAGS: '-march=core2' cpu_CFLAGS: '-march=core2'
amd: amd:
test_value: joinlist: ['second', 'third']
- b
- c
cpu_CFLAGS: '-march=athlon64' cpu_CFLAGS: '-march=athlon64'
arm: arm:
cpu_CFLAGS: '-mabi=apcs-gnu -march=armv8-a -mtune=arm8' cpu_CFLAGS: '-mabi=apcs-gnu -march=armv8-a -mtune=arm8'
disk: disk:
disk_type: 'virtio' disk_type: 'virtio'
corruptlist: 'nonlist'
scsi: scsi:
corruptlist: ['againlist']
disk_type: 'scsi' disk_type: 'scsi'
virtio: virtio:
test_value: 42 corruptlist: ['upper_node_list']
distro: distro:
fedora: fedora:
init: 'systemd' init: 'systemd'
mint: env:
init: 'systemv' opt_CFLAGS: '-Os'
prod:
opt_CFLAGS: 'THIS SHOULD GET OVERWRITTEN'
env: env:
prod: prod:
opt_CFLAGS: '-O2' opt_CFLAGS: '-O2'
distro:
mint:
init: 'systemv'
...@@ -13,6 +13,15 @@ if os.path.isdir(os.path.join(basedir, 'avocado')): ...@@ -13,6 +13,15 @@ if os.path.isdir(os.path.join(basedir, 'avocado')):
from avocado.utils import process from avocado.utils import process
DEBUG_OUT = """Variant 16: amd@examples/mux-environment.yaml, virtio@examples/mux-environment.yaml, mint@examples/mux-environment.yaml, debug@examples/mux-environment.yaml
corruptlist: nonlist@examples/mux-selftest.yaml:/hw/disk
cpu_CFLAGS: -march=athlon64@examples/mux-environment.yaml:/hw/cpu/amd
disk_type: virtio@examples/mux-environment.yaml:/hw/disk/virtio
init: systemv@examples/mux-environment.yaml:/distro/mint
joinlist: ['first_item']@examples/mux-selftest.yaml:/hw/cpu + ['second', 'third']@examples/mux-selftest.yaml:/hw/cpu/amd
opt_CFLAGS: -O0 -g@examples/mux-environment.yaml:/env/debug
"""
class MultiplexTests(unittest.TestCase): class MultiplexTests(unittest.TestCase):
...@@ -51,6 +60,14 @@ class MultiplexTests(unittest.TestCase): ...@@ -51,6 +60,14 @@ class MultiplexTests(unittest.TestCase):
expected_rc = 2 expected_rc = 2
self.run_and_check(cmd_line, expected_rc) self.run_and_check(cmd_line, expected_rc)
def test_mplex_debug(self):
cmd_line = ('./scripts/avocado multiplex -c -d '
'examples/mux-selftest.yaml examples/mux-environment.yaml '
'examples/mux-selftest.yaml examples/mux-environment.yaml')
expected_rc = 0
out = self.run_and_check(cmd_line, expected_rc)
self.assertIn(DEBUG_OUT, out)
def test_run_mplex_noid(self): def test_run_mplex_noid(self):
cmd_line = './scripts/avocado run --multiplex examples/tests/sleeptest.py.data/sleeptest.yaml' cmd_line = './scripts/avocado run --multiplex examples/tests/sleeptest.py.data/sleeptest.yaml'
expected_rc = 2 expected_rc = 2
......
...@@ -17,7 +17,8 @@ class TestTree(unittest.TestCase): ...@@ -17,7 +17,8 @@ class TestTree(unittest.TestCase):
self.tree.children[0].children[0].children[0].value) self.tree.children[0].children[0].children[0].value)
disk = self.tree.children[0].children[1] disk = self.tree.children[0].children[1]
self.assertEqual('scsi', disk.children[0]) self.assertEqual('scsi', disk.children[0])
self.assertEqual({'disk_type': 'scsi'}, disk.children[0].value) self.assertEqual({'disk_type': 'scsi', 'corruptlist': ['againlist']},
disk.children[0].value)
self.assertEqual('virtio', disk.children[1]) self.assertEqual('virtio', disk.children[1])
self.assertEqual({}, disk.children[1].value) self.assertEqual({}, disk.children[1].value)
self.assertEqual('distro', self.tree.children[1]) self.assertEqual('distro', self.tree.children[1])
...@@ -54,6 +55,11 @@ class TestTree(unittest.TestCase): ...@@ -54,6 +55,11 @@ class TestTree(unittest.TestCase):
# Add_child incorrect class # Add_child incorrect class
self.assertRaises(ValueError, tree3.add_child, 'probably_bad_type') self.assertRaises(ValueError, tree3.add_child, 'probably_bad_type')
def test_links(self):
""" Verify child->parent links """
for leaf in self.tree:
self.assertEqual(leaf.root, self.tree)
def test_basic_functions(self): def test_basic_functions(self):
# repr # repr
self.assertEqual("TreeNode(name='hw')", repr(self.tree.children[0])) self.assertEqual("TreeNode(name='hw')", repr(self.tree.children[0]))
...@@ -70,18 +76,36 @@ class TestTree(unittest.TestCase): ...@@ -70,18 +76,36 @@ class TestTree(unittest.TestCase):
) )
# .parents # .parents
self.assertEqual(['hw', ''], self.tree.children[0].children[0].parents) self.assertEqual(['hw', ''], self.tree.children[0].children[0].parents)
# environment # environment / (root)
self.assertEqual({}, self.tree.environment) self.assertEqual({}, self.tree.environment)
self.assertEqual({'test_value': 42}, # environment /hw (nodes first)
self.assertEqual({'corruptlist': ['upper_node_list']},
self.tree.children[0].environment) self.tree.children[0].environment)
cpu = self.tree.children[0].children[0] cpu = self.tree.children[0].children[0]
self.assertEqual({'test_value': ['a']}, # environment /hw/cpu (mixed env)
self.assertEqual({'corruptlist': ['upper_node_list'],
'joinlist': ['first_item']},
cpu.environment) cpu.environment)
vals = {'test_value': ['a', 'b', 'c'], 'cpu_CFLAGS': '-march=athlon64'} # 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) self.assertEqual(vals, cpu.children[1].environment)
vals = {'test_value': ['a'], 'cpu_CFLAGS': '-mabi=apcs-gnu ' # environment /hw/cpu/arm (deep env)
vals = {'corruptlist': ['upper_node_list'], 'joinlist': ['first_item'],
'cpu_CFLAGS': '-mabi=apcs-gnu '
'-march=armv8-a -mtune=arm8'} '-march=armv8-a -mtune=arm8'}
self.assertEqual(vals, cpu.children[2].environment) 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 order
leaves = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', 'mint', leaves = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', 'mint',
'prod'] 'prod']
...@@ -119,9 +143,10 @@ class TestTree(unittest.TestCase): ...@@ -119,9 +143,10 @@ class TestTree(unittest.TestCase):
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'default', 'virtio', exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'default', 'virtio',
'fedora', 'mint', 'prod'] 'fedora', 'mint', 'prod']
self.assertEqual(exp, tree2.get_leaves()) self.assertEqual(exp, tree2.get_leaves())
self.assertEqual({'test_value': 42, 'another_value': 'bbb'}, self.assertEqual({'corruptlist': ['upper_node_list'],
'another_value': 'bbb'},
tree2.children[0].value) tree2.children[0].value)
self.assertEqual({'test_value': ['z']}, self.assertEqual({'joinlist': ['first_item'], 'test_value': ['z']},
tree2.children[0].children[0].value) tree2.children[0].children[0].value)
self.assertFalse(tree2.children[0].children[2].children[0].value) self.assertFalse(tree2.children[0].children[2].children[0].value)
self.assertEqual({'nic': 'virtio'}, self.assertEqual({'nic': 'virtio'},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册