提交 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):
Append node as child. Nodes with the same name gets merged into the
existing position.
"""
if isinstance(node, self.__class__):
if isinstance(node, TreeNode):
if node.name in self.children:
self.children[self.children.index(node.name)].merge(node)
else:
......@@ -288,13 +288,14 @@ class TreeNode(object):
return self
def _create_from_yaml(stream):
""" Create tree structure from yaml stream """
class Value(tuple):
class Value(tuple): # Few methods pylint: disable=R0903
""" 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):
""" Create `name` node and add values """
node_children = []
......@@ -304,10 +305,12 @@ def _create_from_yaml(stream):
node_children.append(value)
else:
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):
""" Maps yaml mapping tag to TreeNode structure """
def is_node(values):
""" Whether these values represent node or just random values """
if (isinstance(values, list) and values
and isinstance(values[0], (Value, TreeNode))):
# When any value is TreeNode or Value, all of them are already
......@@ -320,26 +323,44 @@ def _create_from_yaml(stream):
if is_node(values): # New node
objects.append(tree_node_from_values(name, values))
elif values is None: # Empty node
objects.append(TreeNode(name))
objects.append(cls_node(name))
else: # Values
objects.append(Value((name, values)))
return objects
Loader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
mapping_to_tree_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
:param fileobj: File object to be processed
:raise SyntaxError: When yaml-file is corrupted
:return: Root of the created tree structure
"""
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:
for path in paths:
data.merge(_create_from_yaml(open(path).read()))
merge(data, path)
except (yaml.scanner.ScannerError, yaml.parser.ParserError) as err:
raise SyntaxError(err)
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):
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:
filter_only = []
if filter_out is None:
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)
leaves = (x for x in final_tree.iter_leaves() if x.parent is not None)
variants = multiplex(leaves)
......
......@@ -50,6 +50,9 @@ class Multiplexer(plugin.Plugin):
self.parser.add_argument('-c', '--contents', action='store_true', default=False,
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)
def run(self, args):
......@@ -71,12 +74,19 @@ class Multiplexer(plugin.Plugin):
variants = multiplexer.multiplex_yamls(multiplex_files,
args.filter_only,
args.filter_out)
args.filter_out,
args.debug)
view.notify(event='message', msg='Variants generated:')
for (index, tpl) in enumerate(variants):
if not args.debug:
paths = ', '.join([x.path for x in tpl])
view.notify(event='minor', msg='Variant %s: %s' %
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))
if args.contents:
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:
cpu:
test_value:
- a
joinlist:
- first_item
intel:
cpu_CFLAGS: '-march=core2'
amd:
test_value:
- b
- c
joinlist: ['second', 'third']
cpu_CFLAGS: '-march=athlon64'
arm:
cpu_CFLAGS: '-mabi=apcs-gnu -march=armv8-a -mtune=arm8'
disk:
disk_type: 'virtio'
corruptlist: 'nonlist'
scsi:
corruptlist: ['againlist']
disk_type: 'scsi'
virtio:
test_value: 42
corruptlist: ['upper_node_list']
distro:
fedora:
init: 'systemd'
mint:
init: 'systemv'
env:
opt_CFLAGS: '-Os'
prod:
opt_CFLAGS: 'THIS SHOULD GET OVERWRITTEN'
env:
prod:
opt_CFLAGS: '-O2'
distro:
mint:
init: 'systemv'
......@@ -13,6 +13,15 @@ if os.path.isdir(os.path.join(basedir, 'avocado')):
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):
......@@ -51,6 +60,14 @@ class MultiplexTests(unittest.TestCase):
expected_rc = 2
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):
cmd_line = './scripts/avocado run --multiplex examples/tests/sleeptest.py.data/sleeptest.yaml'
expected_rc = 2
......
......@@ -17,7 +17,8 @@ class TestTree(unittest.TestCase):
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'}, disk.children[0].value)
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])
......@@ -54,6 +55,11 @@ class TestTree(unittest.TestCase):
# Add_child incorrect class
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):
# repr
self.assertEqual("TreeNode(name='hw')", repr(self.tree.children[0]))
......@@ -70,18 +76,36 @@ class TestTree(unittest.TestCase):
)
# .parents
self.assertEqual(['hw', ''], self.tree.children[0].children[0].parents)
# environment
# environment / (root)
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)
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)
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)
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'}
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']
......@@ -119,9 +143,10 @@ class TestTree(unittest.TestCase):
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'default', 'virtio',
'fedora', 'mint', 'prod']
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)
self.assertEqual({'test_value': ['z']},
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'},
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册