Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
avocado
提交
7dfa5a87
A
avocado
项目概览
openeuler
/
avocado
通知
0
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
A
avocado
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
7dfa5a87
编写于
1月 09, 2017
作者:
C
Cleber Rosa
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'ldoktor/mux-rename3'
上级
4c2da0eb
613edb89
变更
18
隐藏空白更改
内联
并排
Showing
18 changed file
with
767 addition
and
487 deletion
+767
-487
avocado/core/job.py
avocado/core/job.py
+19
-18
avocado/core/jobdata.py
avocado/core/jobdata.py
+4
-4
avocado/core/mux.py
avocado/core/mux.py
+162
-0
avocado/core/parser.py
avocado/core/parser.py
+3
-2
avocado/core/remote/runner.py
avocado/core/remote/runner.py
+3
-3
avocado/core/runner.py
avocado/core/runner.py
+9
-9
avocado/core/test.py
avocado/core/test.py
+4
-4
avocado/core/tree.py
avocado/core/tree.py
+0
-95
avocado/core/varianter.py
avocado/core/varianter.py
+26
-30
avocado/plugins/diff.py
avocado/plugins/diff.py
+3
-3
avocado/plugins/multiplex.py
avocado/plugins/multiplex.py
+5
-14
avocado/plugins/replay.py
avocado/plugins/replay.py
+15
-21
avocado/plugins/run.py
avocado/plugins/run.py
+0
-12
avocado/plugins/yaml_to_mux.py
avocado/plugins/yaml_to_mux.py
+74
-16
docs/source/Mux.rst
docs/source/Mux.rst
+26
-26
selftests/functional/test_replay_basic.py
selftests/functional/test_replay_basic.py
+9
-9
selftests/unit/test_mux.py
selftests/unit/test_mux.py
+405
-0
selftests/unit/test_tree.py
selftests/unit/test_tree.py
+0
-221
未找到文件。
avocado/core/job.py
浏览文件 @
7dfa5a87
...
...
@@ -38,7 +38,7 @@ from . import exit_codes
from
.
import
exceptions
from
.
import
job_id
from
.
import
output
from
.
import
multiplex
er
from
.
import
variant
er
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'
...
...
avocado/core/jobdata.py
浏览文件 @
7dfa5a87
...
...
@@ -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
:
...
...
avocado/core/mux.py
0 → 100644
浏览文件 @
7dfa5a87
# 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
avocado/core/parser.py
浏览文件 @
7dfa5a87
...
...
@@ -20,7 +20,7 @@ import argparse
import
logging
from
.
import
exit_codes
from
.
import
multiplex
er
from
.
import
variant
er
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
()
...
...
avocado/core/remote/runner.py
浏览文件 @
7dfa5a87
...
...
@@ -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
()
...
...
avocado/core/runner.py
浏览文件 @
7dfa5a87
...
...
@@ -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"
)
...
...
avocado/core/test.py
浏览文件 @
7dfa5a87
...
...
@@ -28,7 +28,7 @@ import time
from
.
import
data_dir
from
.
import
exceptions
from
.
import
multiplex
er
from
.
import
variant
er
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
=
multiplex
er
.
AvocadoParams
(
params
,
self
.
name
,
mux_path
,
self
.
default_params
)
self
.
params
=
variant
er
.
AvocadoParams
(
params
,
self
.
name
,
mux_path
,
self
.
default_params
)
default_timeout
=
getattr
(
self
,
"timeout"
,
None
)
self
.
timeout
=
self
.
params
.
get
(
"timeout"
,
default
=
default_timeout
)
...
...
avocado/core/tree.py
浏览文件 @
7dfa5a87
...
...
@@ -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.
#
...
...
avocado/core/
multiplex
er.py
→
avocado/core/
variant
er.py
浏览文件 @
7dfa5a87
...
...
@@ -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
:
...
...
avocado/plugins/diff.py
浏览文件 @
7dfa5a87
...
...
@@ -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
:
...
...
avocado/plugins/multiplex.py
浏览文件 @
7dfa5a87
...
...
@@ -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
:
...
...
avocado/plugins/replay.py
浏览文件 @
7dfa5a87
...
...
@@ -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
,
...
...
avocado/plugins/run.py
浏览文件 @
7dfa5a87
...
...
@@ -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'
,
...
...
avocado/plugins/yaml_to_mux.py
浏览文件 @
7dfa5a87
...
...
@@ -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
.
Mux
TreeNode
):
""" 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
.
TreeN
ode
):
if
isinstance
(
value
,
cls_n
ode
):
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
.
Mux
TreeNode
()
merge
=
_merge
else
:
data
=
tree
.
TreeNodeDebug
()
data
=
mux
.
Mux
TreeNodeDebug
()
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
)
docs/source/Mux.rst
浏览文件 @
7dfa5a87
.. _
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
...
...
selftests/functional/test_replay_basic.py
浏览文件 @
7dfa5a87
...
...
@@ -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_ignore
mux
(
self
):
def
test_run_replay_ignore
variants
(
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
):
...
...
selftests/unit/test_mu
ltiplexer
.py
→
selftests/unit/test_mu
x
.py
浏览文件 @
7dfa5a87
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
(
multiplex
er
.
MuxTree
(
self
.
mux_tree
))
self
.
mux_full
=
tuple
(
variant
er
.
MuxTree
(
self
.
mux_tree
))
def
test_empty
(
self
):
act
=
tuple
(
multiplexer
.
MuxTree
(
tree
.
TreeNode
()))
act
=
tuple
(
varianter
.
MuxTree
(
mux
.
Mux
TreeNode
()))
self
.
assertEqual
(
act
,
([
''
,
],))
def
test_partial
(
self
):
exp
=
([
'intel'
,
'scsi'
],
[
'intel'
,
'virtio'
],
[
'amd'
,
'scsi'
],
[
'amd'
,
'virtio'
],
[
'arm'
,
'scsi'
],
[
'arm'
,
'virtio'
])
act
=
tuple
(
multiplex
er
.
MuxTree
(
self
.
mux_tree
.
children
[
0
]))
act
=
tuple
(
variant
er
.
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
=
multiplex
er
.
MuxTree
(
from_file
)
from_file
=
variant
er
.
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
(
multiplex
er
.
MuxTree
(
act
))
act
=
mux
.
apply_filters
(
act
,
(
'/hw/cpu/intel'
,
'/distro/fedora'
,
'/hw'
))
act
=
tuple
(
variant
er
.
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
(
multiplex
er
.
MuxTree
(
act
))
act
=
mux
.
apply_filters
(
act
,
None
,
(
'/hw/cpu/intel'
,
'/distro/fedora'
,
'/distro'
))
act
=
tuple
(
variant
er
.
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
(
multiplex
er
.
MuxTree
(
yamls
))
self
.
params1
=
multiplex
er
.
AvocadoParams
(
self
.
yamls
.
next
(),
'Unittest1'
,
[
'/ch0/*'
,
'/ch1/*'
],
{})
self
.
yamls
=
iter
(
variant
er
.
MuxTree
(
yamls
))
self
.
params1
=
variant
er
.
AvocadoParams
(
self
.
yamls
.
next
(),
'Unittest1'
,
[
'/ch0/*'
,
'/ch1/*'
],
{})
self
.
yamls
.
next
()
# Skip 2nd
self
.
yamls
.
next
()
# and 3rd
self
.
params2
=
multiplex
er
.
AvocadoParams
(
self
.
yamls
.
next
(),
'Unittest2'
,
[
'/ch1/*'
,
'/ch0/*'
],
{})
self
.
params2
=
variant
er
.
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
(
multiplex
er
.
AvocadoParams
([],
'Unittest'
,
[],
{}))
str
(
variant
er
.
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
()
selftests/unit/test_tree.py
已删除
100644 → 0
浏览文件 @
4c2da0eb
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.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录