提交 361948f3 编写于 作者: L Lucas Meneghel Rodrigues

Merge branch 'ldoktor-mux-tree4'

......@@ -14,11 +14,12 @@
import sys
from . import plugin
from .. import output
from .. import multiplexer
from .. import exit_codes
from .. import output
from .. import tree
from .. import multiplexer
from . import plugin
from ..settings import settings
class Multiplexer(plugin.Plugin):
......@@ -30,6 +31,10 @@ class Multiplexer(plugin.Plugin):
name = 'multiplexer'
enabled = True
def __init__(self, *args, **kwargs):
super(Multiplexer, self).__init__(*args, **kwargs)
self._from_args_tree = tree.TreeNode()
def configure(self, parser):
if multiplexer.MULTIPLEX_CAPABLE is False:
self.enabled = False
......@@ -45,18 +50,25 @@ class Multiplexer(plugin.Plugin):
self.parser.add_argument('--filter-out', nargs='*', default=[],
help='Filter out path(s) from multiplexing')
self.parser.add_argument('-t', '--tree', action='store_true', default=False,
help='Shows the multiplex tree structure')
self.parser.add_argument('--attr', nargs='*', default=[],
help="Which attributes to show when using "
"--tree (default is 'name')")
self.parser.add_argument('-c', '--contents', action='store_true', default=False,
help="Shows the variant content (variables)")
self.parser.add_argument('-d', '--debug', action='store_true',
default=False, help="Debug multiplexed "
"files.")
self.parser.add_argument('--env', default=[], nargs='*')
self.parser.add_argument('-s', '--system-wide', action='store_true',
help="Combine the files with the default "
"tree.")
self.parser.add_argument('-c', '--contents', action='store_true',
default=False, help="Shows the node content "
"(variables)")
self.parser.add_argument('--env', default=[], nargs='*',
help="Inject [path:]key:node values into "
"the final multiplex tree.")
env_parser = self.parser.add_argument_group("environment view options")
env_parser.add_argument('-d', '--debug', action='store_true',
default=False, help="Debug multiplexed "
"files.")
tree_parser = self.parser.add_argument_group("tree view options")
tree_parser.add_argument('-t', '--tree', action='store_true',
default=False, help='Shows the multiplex '
'tree structure')
tree_parser.add_argument('-i', '--inherit', action="store_true",
help="Show the inherited values")
super(Multiplexer, self).configure(self.parser)
def activate(self, args):
......@@ -67,13 +79,22 @@ class Multiplexer(plugin.Plugin):
raise ValueError("key:value pairs required, found only %s"
% (value))
elif len(value) == 2:
args.default_multiplex_tree.value[value[0]] = value[1]
self._from_args_tree.value[value[0]] = value[1]
else:
node = args.default_multiplex_tree.get_node(value[0], True)
node = self._from_args_tree.get_node(value[0], True)
node.value[value[1]] = value[2]
def run(self, args):
view = output.View(app_args=args)
err = None
if args.tree and args.debug:
err = "Option --tree is incompatible with --debug."
elif not args.tree and args.inherit:
err = "Option --inherit can be only used with --tree"
if err:
view.notify(event="minor", msg=self.parser.format_help())
view.notify(event="error", msg=err)
sys.exit(exit_codes.AVOCADO_FAIL)
try:
mux_tree = multiplexer.yaml2tree(args.multiplex_files,
args.filter_only, args.filter_out,
......@@ -82,11 +103,20 @@ class Multiplexer(plugin.Plugin):
view.notify(event='error',
msg=details.strerror)
sys.exit(exit_codes.AVOCADO_JOB_FAIL)
mux_tree.merge(args.default_multiplex_tree)
if args.system_wide:
mux_tree.merge(args.default_multiplex_tree)
mux_tree.merge(self._from_args_tree)
if args.tree:
view.notify(event='message', msg='Config file tree structure:')
view.notify(event='minor',
msg=mux_tree.get_ascii(attributes=args.attr))
if args.contents:
verbose = 1
else:
verbose = 0
if args.inherit:
verbose += 2
use_utf8 = settings.get_value("runner.output", "utf8",
key_type=bool, default=None)
view.notify(event='minor', msg=tree.tree_view(mux_tree, verbose,
use_utf8))
sys.exit(exit_codes.AVOCADO_ALL_OK)
variants = multiplexer.MuxTree(mux_tree)
......@@ -105,10 +135,15 @@ class Multiplexer(plugin.Plugin):
view.notify(event='minor', msg='%sVariant %s: %s' %
(('\n' if args.contents else ''), index + 1, paths))
if args.contents:
env = {}
env = set()
for node in tpl:
env.update(node.environment)
for k in sorted(env.keys()):
view.notify(event='minor', msg=' %s: %s' % (k, env[k]))
for key, value in node.environment.iteritems():
origin = node.environment_origin[key].path
env.add(("%s:%s" % (origin, key), str(value)))
if not env:
continue
fmt = ' %%-%ds => %%s' % max([len(_[0]) for _ in env])
for record in sorted(env):
view.notify(event='minor', msg=fmt % record)
sys.exit(exit_codes.AVOCADO_ALL_OK)
......@@ -126,7 +126,9 @@ class TestRunner(plugin.Plugin):
help='Filter out path(s) from multiplexing')
mux.add_argument('--mux-entry', nargs='*', default=None,
help="Multiplex entry point(s)")
mux.add_argument('--env', default=[], nargs='*')
mux.add_argument('--env', default=[], nargs='*',
help="Inject [path:]key:node values into the "
"final multiplex tree.")
super(TestRunner, self).configure(self.parser)
# Export the test runner parser back to the main parser
parser.runner = self.parser
......
......@@ -35,6 +35,7 @@ original base tree code and re-license under GPLv2+, given that GPLv3 and GPLv2
import collections
import itertools
import locale
import os
import re
......@@ -209,6 +210,8 @@ class TreeNode(object):
def get_path(self, sep='/'):
""" Get node path """
if not self.parent:
return sep + str(self.name)
path = [str(self.name)]
for node in self.iter_parents():
path.append(str(node.name))
......@@ -293,72 +296,6 @@ class TreeNode(object):
""" Get list of leaf nodes """
return list(self.iter_leaves())
def get_ascii(self, show_internal=True, compact=False, attributes=None):
"""
Get ascii-art tree structure
:param show_internal: Show intermediary nodes
:param compact: Compress the tree vertically
:param attributes: List of node attributes to be printed out ['name']
:return: string
"""
(lines, _) = self.ascii_art(show_internal=show_internal,
compact=compact, attributes=attributes)
return '\n' + '\n'.join(lines)
def ascii_art(self, char1='-', show_internal=True, compact=False,
attributes=None):
"""
Generate ascii-art for this node
:param char1: Incomming path character [-]
:param show_internal: Show intermediary nodes
:param compact: Compress the tree vertically
:param attributes: List of node attributes to be printed out ['name']
:return: list of strings
"""
if not attributes:
attributes = ["name"]
node_name = ', '.join(map(str, [getattr(self, v)
for v in attributes
if hasattr(self, v)]))
if self.multiplex:
node_name += "-<>"
length = max(2, (len(node_name) + 1) if not self.children or show_internal else 3)
pad = ' ' * length
_pad = ' ' * (length - 1)
if not self.is_leaf:
mids = []
result = []
for char in self.children:
if len(self.children) == 1:
char2 = '-'
elif char is self.children[0]:
char2 = '/'
elif char is self.children[-1]:
char2 = '\\'
else:
char2 = '-'
(clines, mid) = char.ascii_art(char2, show_internal, compact,
attributes)
mids.append(mid + len(result))
result.extend(clines)
if not compact:
result.append('')
if not compact:
result.pop()
(low, high, end) = (mids[0], mids[-1], len(result))
prefixes = ([pad] * (low + 1) + [_pad + '|'] * (high - low - 1) +
[pad] * (end - high))
mid = (low + high) / 2
prefixes[mid] = char1 + '-' * (length - 2) + prefixes[mid][-1]
result = [p + l for (p, l) in zip(prefixes, result)]
if show_internal:
stem = result[mid]
result[mid] = stem[0] + node_name + stem[len(node_name) + 1:]
return result, mid
else:
return [char1 + '-' + node_name], 0
def detach(self):
""" Detach this node from parent """
if self.parent:
......@@ -540,6 +477,8 @@ def path_parent(path):
:return: the parent path as string.
"""
parent = path.rpartition('/')[0]
if not parent:
return '/'
return parent
......@@ -719,3 +658,114 @@ def get_named_tree_cls(path):
children,
path.split(':', 1)[-1])
return NamedTreeNodeDebug
def tree_view(root, verbose=None, use_utf8=None):
"""
Generate tree-view of the given node
:param root: root node
:param verbose: verbosity (0, 1, 2, 3)
:param use_utf8: Use utf-8 encoding (None=autodetect)
:return: string representing this node's tree structure
"""
def prefixed_write(prefix1, prefix2, value):
"""
Split value's lines and prepend empty prefix to 2nd+ lines
:return: list of lines
"""
value = str(value)
if '\n' not in value:
return [prefix1 + prefix2 + value]
value = value.splitlines()
empty_prefix2 = ' ' * len(prefix2)
return [prefix1 + prefix2 + value[0]] + [prefix1 + empty_prefix2 +
_ for _ in value[1:]]
def process_node(node):
"""
Generate this node's tree-view
:return: list of lines
"""
if node.multiplex:
down = charset['DoubleDown']
down_right = charset['DoubleDownRight']
right = charset['DoubleRight']
else:
down = charset['Down']
down_right = charset['DownRight']
right = charset['Right']
out = [node.name]
if verbose >= 2 and node.is_leaf:
values = node.environment.iteritems()
elif verbose in (1, 3):
values = node.value.iteritems()
else:
values = None
if values:
val = charset['Value']
if node.children:
val_prefix = down
else:
val_prefix = ' '
for key, value in values:
out.extend(prefixed_write(val_prefix, val + key + ': ',
value))
if node.children:
for child in node.children[:-1]:
lines = process_node(child)
out.append(down_right + lines[0])
out.extend(down + line for line in lines[1:])
lines = process_node(node.children[-1])
out.append(right + lines[0])
empty_down_right = ' ' * len(down_right)
out.extend(empty_down_right + line for line in lines[1:])
return out
if use_utf8 is None:
use_utf8 = locale.getdefaultlocale()[1] == 'UTF-8'
if use_utf8:
charset = {'DoubleDown': u' \u2551 ',
'DoubleDownRight': u' \u2560\u2550\u2550 ',
'DoubleRight': u' \u255a\u2550\u2550 ',
'Down': u' \u2503 ',
'DownRight': u' \u2523\u2501\u2501 ',
'Right': u' \u2517\u2501\u2501 ',
'Value': u'\u2192 '}
else: # ASCII fallback
charset = {'Down': ' | ',
'DownRight': ' |-- ',
'Right': ' \\-- ',
'DoubleDown': ' # ',
'DoubleDownRight': ' #== ',
'DoubleRight': ' #== ',
'Value': ' -> '}
if root.multiplex:
down = charset['DoubleDown']
down_right = charset['DoubleDownRight']
right = charset['DoubleRight']
else:
down = charset['Down']
down_right = charset['DownRight']
right = charset['Right']
out = []
if (verbose >= 2) and root.is_leaf:
values = root.environment.iteritems()
elif verbose in (1, 3):
values = root.value.iteritems()
else:
values = None
if values:
prefix = charset['Value'].lstrip()
for key, value in values:
out.extend(prefixed_write(prefix, key + ': ', value))
if root.children:
for child in root.children[:-1]:
lines = process_node(child)
out.append(down_right + lines[0])
out.extend(down + line for line in lines[1:])
lines = process_node(root.children[-1])
out.append(right + lines[0])
out.extend(' ' * len(down_right) + line for line in lines[1:])
# When not on TTY we need to force the encoding
return '\n'.join(out).encode('utf-8' if use_utf8 else 'ascii')
......@@ -27,6 +27,8 @@ profilers = /etc/avocado/sysinfo/profilers
[runner.output]
# Whether to display colored output in terminals that support it
colored = True
# Use utf8 encoding (True, False, None=autodetect)
utf8 =
[runner.behavior]
# Keep job temporary files after jobs (useful for avocado debugging)
......
......@@ -20,12 +20,13 @@ 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
/distro/mint:init => systemv@examples/mux-environment.yaml:/distro/mint
/env/debug:opt_CFLAGS => -O0 -g@examples/mux-environment.yaml:/env/debug
/hw/cpu/amd:cpu_CFLAGS => -march=athlon64@examples/mux-environment.yaml:/hw/cpu/amd
/hw/cpu/amd:joinlist => ['first_item']@examples/mux-selftest.yaml:/hw/cpu + ['second', 'third']@examples/mux-selftest.yaml:/hw/cpu/amd
/hw/disk/virtio:disk_type => virtio@examples/mux-environment.yaml:/hw/disk/virtio
/hw/disk:corruptlist => nonlist@examples/mux-selftest.yaml:/hw/disk
/hw:corruptlist => ['upper_node_list']@examples/mux-selftest.yaml:/hw
"""
......
......@@ -8,10 +8,16 @@ else:
from avocado.core import tree
if __name__ == "__main__":
PATH_PREFIX = "../../../../"
else:
PATH_PREFIX = ""
class TestTree(unittest.TestCase):
# Share tree with all tests
tree = tree.create_from_yaml(['/:examples/mux-selftest.yaml'])
tree = tree.create_from_yaml(['/:' + PATH_PREFIX +
'examples/mux-selftest.yaml'])
def test_node_order(self):
self.assertIsInstance(self.tree, tree.TreeNode)
......@@ -112,7 +118,7 @@ class TestTree(unittest.TestCase):
'prod']
self.assertEqual(leaves, self.tree.get_leaves())
# asci contain all leaves and doesn't raise any exceptions
ascii = self.tree.get_ascii()
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))
......@@ -154,8 +160,8 @@ class TestTree(unittest.TestCase):
tree2.children[0].children[2].children[1].value)
def test_advanced_yaml(self):
tree2 = tree.create_from_yaml(['/:examples/mux-selftest-advanced.'
'yaml'])
tree2 = tree.create_from_yaml(['/:' + PATH_PREFIX + 'examples/mux-'
'selftest-advanced.yaml'])
exp = ['intel', 'amd', 'arm', 'scsi', 'virtio', 'fedora', '6',
'7', 'gentoo', 'mint', 'prod', 'new_node']
act = tree2.get_leaves()
......@@ -190,10 +196,10 @@ class TestTree(unittest.TestCase):
class TestPathParent(unittest.TestCase):
def test_empty_string(self):
self.assertEqual(tree.path_parent(''), '')
self.assertEqual(tree.path_parent(''), '/')
def test_on_root(self):
self.assertEqual(tree.path_parent('/'), '')
self.assertEqual(tree.path_parent('/'), '/')
def test_direct_parent(self):
self.assertEqual(tree.path_parent('/os/linux'), '/os')
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册