Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
avocado
提交
ac035ebb
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,发现更多精彩内容 >>
未验证
提交
ac035ebb
编写于
10月 07, 2016
作者:
C
Cleber Rosa
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'ldoktor/mux-plugin4'
上级
b31696b5
add27f8a
变更
23
隐藏空白更改
内联
并排
Showing
23 changed file
with
653 addition
and
443 deletion
+653
-443
avocado/core/job.py
avocado/core/job.py
+7
-6
avocado/core/jobdata.py
avocado/core/jobdata.py
+1
-1
avocado/core/multiplexer.py
avocado/core/multiplexer.py
+84
-23
avocado/core/parser.py
avocado/core/parser.py
+5
-2
avocado/core/remote/runner.py
avocado/core/remote/runner.py
+3
-3
avocado/core/tree.py
avocado/core/tree.py
+5
-196
avocado/plugins/multiplex.py
avocado/plugins/multiplex.py
+13
-38
avocado/plugins/replay.py
avocado/plugins/replay.py
+26
-18
avocado/plugins/run.py
avocado/plugins/run.py
+41
-61
avocado/plugins/yaml_to_mux.py
avocado/plugins/yaml_to_mux.py
+279
-0
docs/source/DebuggingWithGDB.rst
docs/source/DebuggingWithGDB.rst
+1
-1
docs/source/Mux.rst
docs/source/Mux.rst
+106
-19
docs/source/Replay.rst
docs/source/Replay.rst
+1
-1
docs/source/WritingTests.rst
docs/source/WritingTests.rst
+15
-15
docs/source/index.rst
docs/source/index.rst
+1
-1
selftests/.data/empty_file
selftests/.data/empty_file
+0
-0
selftests/functional/test_multiplex.py
selftests/functional/test_multiplex.py
+25
-14
selftests/functional/test_replay_basic.py
selftests/functional/test_replay_basic.py
+5
-6
selftests/functional/test_replay_external_runner.py
selftests/functional/test_replay_external_runner.py
+1
-2
selftests/unit/test_multiplexer.py
selftests/unit/test_multiplexer.py
+24
-29
selftests/unit/test_remote.py
selftests/unit/test_remote.py
+3
-3
selftests/unit/test_tree.py
selftests/unit/test_tree.py
+6
-4
setup.py
setup.py
+1
-0
未找到文件。
avocado/core/job.py
浏览文件 @
ac035ebb
...
...
@@ -446,14 +446,15 @@ class Job(object):
"command."
)
raise
exceptions
.
OptionValidationError
(
e_msg
)
if
isinstance
(
getattr
(
self
.
args
,
'multiplex_files'
,
None
),
multiplexer
.
Mux
)
:
mux
=
self
.
args
.
multiplex_files
# pylint: disable=E1101
else
:
mux
=
getattr
(
self
.
args
,
"mux"
,
None
)
if
mux
is
None
:
mux
=
multiplexer
.
Mux
()
if
not
mux
.
is_parsed
():
# Mux not yet parsed, apply args
try
:
mux
=
multiplexer
.
Mux
(
self
.
args
)
mux
.
parse
(
self
.
args
)
except
(
IOError
,
ValueError
)
as
details
:
raise
exceptions
.
OptionValidationError
(
details
)
raise
exceptions
.
OptionValidationError
(
"Unable to parse mux: "
"%s"
%
details
)
self
.
args
.
test_result_total
=
mux
.
get_number_of_tests
(
self
.
test_suite
)
self
.
_make_test_result
()
...
...
avocado/core/jobdata.py
浏览文件 @
ac035ebb
...
...
@@ -100,7 +100,7 @@ def retrieve_urls(resultsdir):
def
retrieve_mux
(
resultsdir
):
"""
Retrieves the job
multiplex
from the results directory.
Retrieves the job
Mux object
from the results directory.
"""
recorded_mux
=
_retrieve
(
resultsdir
,
MUX_FILENAME
)
if
recorded_mux
is
None
:
...
...
avocado/core/multiplexer.py
浏览文件 @
ac035ebb
...
...
@@ -26,8 +26,6 @@ import re
from
.
import
tree
MULTIPLEX_CAPABLE
=
tree
.
MULTIPLEX_CAPABLE
class
MuxTree
(
object
):
...
...
@@ -80,18 +78,6 @@ class MuxTree(object):
yield
list
(
itertools
.
chain
(
*
pools
.
next
()))
def
yaml2tree
(
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
,
debug
)
# TODO: Process filters and multiplex simultaneously
final_tree
=
tree
.
apply_filters
(
input_tree
,
filter_only
,
filter_out
)
return
final_tree
# TODO: Create multiplexer plugin and split these functions into multiple files
class
NoMatchError
(
KeyError
):
pass
...
...
@@ -377,28 +363,103 @@ 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
):
"""
This is a multiplex object which multiplexes the test_suite.
"""
def
__init__
(
self
,
args
):
def
__init__
(
self
,
debug
=
False
):
"""
:param debug: Store whether this instance should debug the mux
:note: people need to check whether mux uses debug and reflect that
in order to provide the right results.
"""
self
.
_has_multiple_variants
=
None
mux_files
=
getattr
(
args
,
'multiplex_files'
,
None
)
self
.
variants
=
None
self
.
debug
=
debug
self
.
data
=
tree
.
TreeNodeDebug
()
if
debug
else
tree
.
TreeNode
()
self
.
_mux_path
=
None
def
parse
(
self
,
args
):
"""
Apply options defined on the cmdline
:param args: Parsed cmdline arguments
"""
filter_only
=
getattr
(
args
,
'filter_only'
,
None
)
filter_out
=
getattr
(
args
,
'filter_out'
,
None
)
if
mux_files
:
mux_tree
=
yaml2tree
(
mux_files
)
else
:
# no variants
mux_tree
=
tree
.
TreeNode
()
if
getattr
(
args
,
'default_avocado_params'
,
None
):
mux_tree
.
merge
(
args
.
default_avocado_params
)
mux_tree
=
tree
.
apply_filters
(
mux_tree
,
filter_only
,
filter_out
)
self
.
_parse_basic_injects
(
args
)
mux_tree
=
tree
.
apply_filters
(
self
.
data
,
filter_only
,
filter_out
)
self
.
variants
=
MuxTree
(
mux_tree
)
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
:param args: Parsed cmdline arguments
"""
# FIXME: Backward compatibility params, to be removed when 36 LTS is
# discontinued
if
(
not
getattr
(
args
,
"mux_skip_defaults"
,
False
)
and
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
data_inject
(
self
,
key
,
value
,
path
=
None
):
"""
Inject entry to the mux tree (params database)
:param key: Key to which we'd like to assign the value
:param value: The key's value
:param path: Optional path to the node to which we assign the value,
by default '/'.
"""
if
path
:
node
=
self
.
data
.
get_node
(
path
,
True
)
else
:
node
=
self
.
data
node
.
value
[
key
]
=
value
def
data_merge
(
self
,
tree
):
"""
Merge tree into the mux tree (params database)
:param tree: Tree to be merged into this database.
:type tree: :class:`avocado.core.tree.TreeNode`
"""
self
.
data
.
merge
(
tree
)
def
get_number_of_tests
(
self
,
test_suite
):
"""
...
...
avocado/core/parser.py
浏览文件 @
ac035ebb
...
...
@@ -12,7 +12,6 @@
# Copyright: Red Hat Inc. 2013-2014
# Author: Ruda Moura <rmoura@redhat.com>
"""
Avocado application command line parsing.
"""
...
...
@@ -21,8 +20,9 @@ import argparse
import
logging
from
.
import
exit_codes
from
.
import
tree
from
.
import
multiplexer
from
.
import
settings
from
.
import
tree
from
.output
import
BUILTIN_STREAMS
,
BUILTIN_STREAM_SETS
from
.version
import
VERSION
...
...
@@ -125,6 +125,9 @@ class Parser(object):
dest
=
'subcommand'
)
# Allow overriding default params by plugins
self
.
args
.
mux
=
multiplexer
.
Mux
(
getattr
(
self
.
args
,
"mux-debug"
,
False
))
# FIXME: Backward compatibility params, to be removed when 36 LTS is
# discontinued
self
.
args
.
default_avocado_params
=
tree
.
TreeNode
()
def
finish
(
self
):
...
...
avocado/core/remote/runner.py
浏览文件 @
ac035ebb
...
...
@@ -80,7 +80,7 @@ class RemoteTestRunner(TestRunner):
test_data
=
path
+
'.data'
if
os
.
path
.
isdir
(
test_data
):
self
.
remote
.
send_files
(
test_data
,
os
.
path
.
dirname
(
rpath
))
for
mux_file
in
getattr
(
self
.
job
.
args
,
'multiplex
_files
'
)
or
[]:
for
mux_file
in
getattr
(
self
.
job
.
args
,
'multiplex'
)
or
[]:
rpath
=
os
.
path
.
join
(
self
.
remote_test_dir
,
mux_file
)
self
.
remote
.
makedir
(
os
.
path
.
dirname
(
rpath
))
self
.
remote
.
send_files
(
mux_file
,
rpath
)
...
...
@@ -181,9 +181,9 @@ class RemoteTestRunner(TestRunner):
extra_params
=
[]
mux_files
=
[
os
.
path
.
join
(
self
.
remote_test_dir
,
mux_file
)
for
mux_file
in
getattr
(
self
.
job
.
args
,
'multiplex
_files
'
)
or
[]]
'multiplex'
)
or
[]]
if
mux_files
:
extra_params
.
append
(
"-
-multiplex
%s"
%
" "
.
join
(
mux_files
))
extra_params
.
append
(
"-
m
%s"
%
" "
.
join
(
mux_files
))
if
getattr
(
self
.
job
.
args
,
"dry_run"
,
False
):
extra_params
.
append
(
"--dry-run"
)
...
...
avocado/core/tree.py
浏览文件 @
ac035ebb
...
...
@@ -39,28 +39,12 @@ import locale
import
os
import
re
try
:
import
yaml
except
ImportError
:
MULTIPLEX_CAPABLE
=
False
else
:
MULTIPLEX_CAPABLE
=
True
try
:
from
yaml
import
CLoader
as
Loader
except
ImportError
:
from
yaml
import
Loader
from
.
import
output
# Mapping for yaml flags
YAML_INCLUDE
=
0
YAML_USING
=
1
YAML_REMOVE_NODE
=
2
YAML_REMOVE_VALUE
=
3
YAML_MUX
=
4
__RE_FILE_SPLIT
=
re
.
compile
(
r
'(?<!\\):'
)
# split by ':' but not '\\:'
__RE_FILE_SUBS
=
re
.
compile
(
r
'(?<!\\)\\:'
)
# substitute '\\:' but not '\\\\:'
# Tags to remove node/value
REMOVE_NODE
=
0
REMOVE_VALUE
=
1
class
Control
(
object
):
# Few methods pylint: disable=R0903
...
...
@@ -144,7 +128,7 @@ class TreeNode(object):
"""
for
ctrl
in
other
.
ctrl
:
if
isinstance
(
ctrl
,
Control
):
if
ctrl
.
code
==
YAML_
REMOVE_NODE
:
if
ctrl
.
code
==
REMOVE_NODE
:
remove
=
[]
regexp
=
re
.
compile
(
ctrl
.
value
)
for
child
in
self
.
children
:
...
...
@@ -152,7 +136,7 @@ class TreeNode(object):
remove
.
append
(
child
)
for
child
in
remove
:
self
.
children
.
remove
(
child
)
elif
ctrl
.
code
==
YAML_
REMOVE_VALUE
:
elif
ctrl
.
code
==
REMOVE_VALUE
:
remove
=
[]
regexp
=
re
.
compile
(
ctrl
.
value
)
for
key
in
self
.
value
.
iterkeys
():
...
...
@@ -304,181 +288,6 @@ class TreeNode(object):
return
self
class
Value
(
tuple
):
# Few methods pylint: disable=R0903
""" Used to mark values to simplify checking for node vs. value """
pass
class
ListOfNodeObjects
(
list
):
# Few methods pylint: disable=R0903
"""
Used to mark list as list of objects from whose node is going to be created
"""
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
=
cls_node
(
str
(
name
))
using
=
''
for
value
in
values
:
if
isinstance
(
value
,
TreeNode
):
node
.
add_child
(
value
)
elif
isinstance
(
value
[
0
],
Control
):
if
value
[
0
].
code
==
YAML_INCLUDE
:
# Include file
ypath
=
value
[
1
]
if
not
os
.
path
.
isabs
(
ypath
):
ypath
=
os
.
path
.
join
(
os
.
path
.
dirname
(
path
),
ypath
)
if
not
os
.
path
.
exists
(
ypath
):
raise
ValueError
(
"File '%s' included from '%s' does not "
"exist."
%
(
ypath
,
path
))
node
.
merge
(
_create_from_yaml
(
'/:'
+
ypath
,
cls_node
))
elif
value
[
0
].
code
==
YAML_USING
:
if
using
:
raise
ValueError
(
"!using can be used only once per "
"node! (%s:%s)"
%
(
path
,
name
))
using
=
value
[
1
]
if
using
[
0
]
==
'/'
:
using
=
using
[
1
:]
if
using
[
-
1
]
==
'/'
:
using
=
using
[:
-
1
]
elif
value
[
0
].
code
==
YAML_REMOVE_NODE
:
value
[
0
].
value
=
value
[
1
]
# set the name
node
.
ctrl
.
append
(
value
[
0
])
# add "blue pill" of death
elif
value
[
0
].
code
==
YAML_REMOVE_VALUE
:
value
[
0
].
value
=
value
[
1
]
# set the name
node
.
ctrl
.
append
(
value
[
0
])
elif
value
[
0
].
code
==
YAML_MUX
:
node
.
multiplex
=
True
else
:
node
.
value
[
value
[
0
]]
=
value
[
1
]
if
using
:
if
name
is
not
''
:
for
name
in
using
.
split
(
'/'
)[::
-
1
]:
node
=
cls_node
(
name
,
children
=
[
node
])
else
:
using
=
using
.
split
(
'/'
)[::
-
1
]
node
.
name
=
using
.
pop
()
while
True
:
if
not
using
:
break
name
=
using
.
pop
()
# 'using' is list pylint: disable=E1101
node
=
cls_node
(
name
,
children
=
[
node
])
node
=
cls_node
(
''
,
children
=
[
node
])
return
node
def
mapping_to_tree_loader
(
loader
,
node
):
""" Maps yaml mapping tag to TreeNode structure """
_value
=
[]
for
key_node
,
value_node
in
node
.
value
:
if
key_node
.
tag
.
startswith
(
'!'
):
# reflect tags everywhere
key
=
loader
.
construct_object
(
key_node
)
else
:
key
=
loader
.
construct_python_str
(
key_node
)
value
=
loader
.
construct_object
(
value_node
)
_value
.
append
((
key
,
value
))
objects
=
ListOfNodeObjects
()
for
name
,
values
in
_value
:
if
isinstance
(
values
,
ListOfNodeObjects
):
# New node from list
objects
.
append
(
tree_node_from_values
(
name
,
values
))
elif
values
is
None
:
# Empty node
objects
.
append
(
cls_node
(
str
(
name
)))
else
:
# Values
objects
.
append
(
Value
((
name
,
values
)))
return
objects
def
mux_loader
(
loader
,
obj
):
"""
Special !mux loader which allows to tag node as 'multiplex = True'.
"""
if
not
isinstance
(
obj
,
yaml
.
ScalarNode
):
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
((
Control
(
YAML_MUX
),
None
))
return
objects
Loader
.
add_constructor
(
u
'!include'
,
lambda
loader
,
node
:
Control
(
YAML_INCLUDE
))
Loader
.
add_constructor
(
u
'!using'
,
lambda
loader
,
node
:
Control
(
YAML_USING
))
Loader
.
add_constructor
(
u
'!remove_node'
,
lambda
loader
,
node
:
Control
(
YAML_REMOVE_NODE
))
Loader
.
add_constructor
(
u
'!remove_value'
,
lambda
loader
,
node
:
Control
(
YAML_REMOVE_VALUE
))
Loader
.
add_constructor
(
u
'!mux'
,
mux_loader
)
Loader
.
add_constructor
(
yaml
.
resolver
.
BaseResolver
.
DEFAULT_MAPPING_TAG
,
mapping_to_tree_loader
)
# Parse file name ([$using:]$path)
path
=
__RE_FILE_SPLIT
.
split
(
path
,
1
)
if
len
(
path
)
==
1
:
path
=
__RE_FILE_SUBS
.
sub
(
':'
,
path
[
0
])
using
=
[
"run"
]
else
:
nodes
=
__RE_FILE_SUBS
.
sub
(
':'
,
path
[
0
]).
strip
(
'/'
).
split
(
'/'
)
using
=
[
node
for
node
in
nodes
if
node
]
if
not
path
[
0
].
startswith
(
'/'
):
# relative path, put into /run
using
.
insert
(
0
,
'run'
)
path
=
__RE_FILE_SUBS
.
sub
(
':'
,
path
[
1
])
# Load the tree
with
open
(
path
)
as
stream
:
loaded_tree
=
yaml
.
load
(
stream
,
Loader
)
loaded_tree
=
tree_node_from_values
(
''
,
loaded_tree
)
# Add prefix
if
using
:
loaded_tree
.
name
=
using
.
pop
()
while
True
:
if
not
using
:
break
loaded_tree
=
cls_node
(
using
.
pop
(),
children
=
[
loaded_tree
])
loaded_tree
=
cls_node
(
''
,
children
=
[
loaded_tree
])
return
loaded_tree
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
=
get_named_tree_cls
(
path
)
data
.
merge
(
_create_from_yaml
(
path
,
node_cls
))
if
not
debug
:
data
=
TreeNode
()
merge
=
_merge
else
:
data
=
TreeNodeDebug
()
merge
=
_merge_debug
path
=
None
try
:
for
path
in
paths
:
merge
(
data
,
path
)
# Yaml can raise IndexError on some files
except
(
yaml
.
YAMLError
,
IndexError
)
as
details
:
if
'mapping values are not allowed in this context'
in
str
(
details
):
details
=
(
"%s
\n
Make sure !tags and colons are separated by a "
"space (eg. !include :)"
%
details
)
msg
=
"Invalid multiplex file '%s': %s"
%
(
path
,
details
)
raise
IOError
(
2
,
msg
,
path
)
return
data
def
path_parent
(
path
):
"""
From a given path, return its parent path.
...
...
avocado/plugins/multiplex.py
浏览文件 @
ac035ebb
...
...
@@ -16,7 +16,6 @@ import logging
import
sys
from
avocado.core
import
exit_codes
,
output
from
avocado.core
import
multiplexer
from
avocado.core
import
tree
from
avocado.core.plugin_interfaces
import
CLICmd
from
avocado.core.settings
import
settings
...
...
@@ -29,26 +28,21 @@ class Multiplex(CLICmd):
"""
name
=
'multiplex'
description
=
'Generate a list of dictionaries with params from a multiplex file'
description
=
"Tool to analyze and visualize test variants and params"
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
Multiplex
,
self
).
__init__
(
*
args
,
**
kwargs
)
self
.
_from_args_tree
=
tree
.
TreeNode
()
def
configure
(
self
,
parser
):
if
multiplexer
.
MULTIPLEX_CAPABLE
is
False
:
return
parser
=
super
(
Multiplex
,
self
).
configure
(
parser
)
parser
.
add_argument
(
'multiplex_files'
,
nargs
=
'+'
,
help
=
'Path(s) to a multiplex file(s)'
)
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_true'
,
parser
.
add_argument
(
'--system-wide'
,
action
=
'store_false'
,
default
=
True
,
dest
=
"mux-skip-defaults"
,
help
=
"Combine the files with the default "
"tree."
)
parser
.
add_argument
(
'-c'
,
'--contents'
,
action
=
'store_true'
,
...
...
@@ -59,8 +53,8 @@ class Multiplex(CLICmd):
"the final multiplex tree."
)
env_parser
=
parser
.
add_argument_group
(
"environment view options"
)
env_parser
.
add_argument
(
'-d'
,
'--debug'
,
action
=
'store_true'
,
de
fault
=
False
,
help
=
"Debug multiplexed "
"files
."
)
de
st
=
"mux_debug"
,
default
=
False
,
help
=
"Debug the multiplex tree
."
)
tree_parser
=
parser
.
add_argument_group
(
"tree view options"
)
tree_parser
.
add_argument
(
'-t'
,
'--tree'
,
action
=
'store_true'
,
default
=
False
,
help
=
'Shows the multiplex '
...
...
@@ -68,40 +62,22 @@ class Multiplex(CLICmd):
tree_parser
.
add_argument
(
'-i'
,
'--inherit'
,
action
=
"store_true"
,
help
=
"Show the inherited values"
)
def
_activate
(
self
,
args
):
# Extend default multiplex tree of --env values
for
value
in
getattr
(
args
,
"mux_inject"
,
[]):
value
=
value
.
split
(
':'
,
2
)
if
len
(
value
)
<
2
:
raise
ValueError
(
"key:value pairs required, found only %s"
%
(
value
))
elif
len
(
value
)
==
2
:
self
.
_from_args_tree
.
value
[
value
[
0
]]
=
value
[
1
]
else
:
node
=
self
.
_from_args_tree
.
get_node
(
value
[
0
],
True
)
node
.
value
[
value
[
1
]]
=
value
[
2
]
def
run
(
self
,
args
):
self
.
_activate
(
args
)
log
=
logging
.
getLogger
(
"avocado.app"
)
err
=
None
if
args
.
tree
and
args
.
debug
:
if
args
.
tree
and
args
.
mux_
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
:
log
.
error
(
err
)
sys
.
exit
(
exit_codes
.
AVOCADO_FAIL
)
mux
=
args
.
mux
try
:
mux_tree
=
multiplexer
.
yaml2tree
(
args
.
multiplex_files
,
args
.
filter_only
,
args
.
filter_out
,
args
.
debug
)
except
IOError
as
details
:
log
.
error
(
details
.
strerror
)
mux
.
parse
(
args
)
except
(
IOError
,
ValueError
)
as
details
:
log
.
error
(
"Unable to parse mux: %s"
,
details
)
sys
.
exit
(
exit_codes
.
AVOCADO_JOB_FAIL
)
if
args
.
system_wide
:
mux_tree
.
merge
(
args
.
default_avocado_params
)
mux_tree
.
merge
(
self
.
_from_args_tree
)
if
args
.
tree
:
if
args
.
contents
:
verbose
=
1
...
...
@@ -111,13 +87,12 @@ class Multiplex(CLICmd):
verbose
+=
2
use_utf8
=
settings
.
get_value
(
"runner.output"
,
"utf8"
,
key_type
=
bool
,
default
=
None
)
log
.
debug
(
tree
.
tree_view
(
mux
_tree
,
verbose
,
use_utf8
))
log
.
debug
(
tree
.
tree_view
(
mux
.
variants
.
root
,
verbose
,
use_utf8
))
sys
.
exit
(
exit_codes
.
AVOCADO_ALL_OK
)
variants
=
multiplexer
.
MuxTree
(
mux_tree
)
log
.
info
(
'Variants generated:'
)
for
(
index
,
tpl
)
in
enumerate
(
variants
):
if
not
args
.
debug
:
for
(
index
,
tpl
)
in
enumerate
(
mux
.
variants
):
if
not
args
.
mux_
debug
:
paths
=
', '
.
join
([
x
.
path
for
x
in
tpl
])
else
:
color
=
output
.
TERM_SUPPORT
.
LOWLIGHT
...
...
avocado/plugins/replay.py
浏览文件 @
ac035ebb
...
...
@@ -27,6 +27,12 @@ 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
):
"""
...
...
@@ -57,7 +63,7 @@ class Replay(CLI):
replay_parser
.
add_argument
(
'--replay-ignore'
,
dest
=
'replay_ignore'
,
type
=
self
.
_valid_ignore
,
default
=
None
,
default
=
[]
,
help
=
'Ignore multiplex (mux) and/or '
'configuration (config) from the '
'source job'
)
...
...
@@ -125,9 +131,9 @@ class Replay(CLI):
log
=
logging
.
getLogger
(
"avocado.app"
)
err
=
None
if
args
.
replay_teststatus
and
args
.
multiplex_files
:
err
=
(
"Option
--replay-test-status
is incompatible with "
"
--multiplex
."
)
if
args
.
replay_teststatus
and
'mux'
in
args
.
replay_ignore
:
err
=
(
"Option
`--replay-test-status`
is incompatible with "
"
`--replay-ignore mux`
."
)
elif
args
.
replay_teststatus
and
args
.
url
:
err
=
(
"Option --replay-test-status is incompatible with "
"test URLs given on the command line."
)
...
...
@@ -195,29 +201,31 @@ class Replay(CLI):
else
:
setattr
(
args
,
'url'
,
urls
)
if
args
.
replay_ignore
and
'config'
in
args
.
replay_ignore
:
if
'config'
in
args
.
replay_ignore
:
log
.
warn
(
"Ignoring configuration from source job with "
"--replay-ignore."
)
else
:
self
.
load_config
(
resultsdir
)
if
args
.
replay_ignore
and
'mux'
in
args
.
replay_ignore
:
if
'mux'
in
args
.
replay_ignore
:
log
.
warn
(
"Ignoring multiplex from source job with "
"--replay-ignore."
)
else
:
if
getattr
(
args
,
'multiplex_files'
,
None
)
is
not
None
:
log
.
warn
(
'Overriding the replay multiplex with '
'--multiplex-file.'
)
# Use absolute paths to avoid problems with os.chdir
args
.
multiplex_files
=
[
os
.
path
.
abspath
(
_
)
for
_
in
args
.
multiplex_files
]
mux
=
jobdata
.
retrieve_mux
(
resultsdir
)
if
mux
is
None
:
log
.
error
(
'Source job multiplex data not found. Aborting.'
)
sys
.
exit
(
exit_codes
.
AVOCADO_JOB_FAIL
)
else
:
mux
=
jobdata
.
retrieve_mux
(
resultsdir
)
if
mux
is
None
:
log
.
error
(
'Source job multiplex data not found. Aborting.'
)
sys
.
exit
(
exit_codes
.
AVOCADO_JOB_FAIL
)
else
:
setattr
(
args
,
"multiplex_files"
,
mux
)
# 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
:
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
if
args
.
replay_teststatus
:
replay_map
=
self
.
_create_replay_map
(
resultsdir
,
...
...
avocado/plugins/run.py
浏览文件 @
ac035ebb
...
...
@@ -23,7 +23,6 @@ import sys
from
avocado.core
import
exit_codes
from
avocado.core
import
job
from
avocado.core
import
loader
from
avocado.core
import
multiplexer
from
avocado.core.plugin_interfaces
import
CLICmd
from
avocado.core.dispatcher
import
ResultDispatcher
from
avocado.core.settings
import
settings
...
...
@@ -37,7 +36,8 @@ class Run(CLICmd):
"""
name
=
'run'
description
=
'Run one or more tests (native test, test alias, binary or script)'
description
=
(
"Runs one or more tests (native test, test alias, binary"
"or script)"
)
def
configure
(
self
,
parser
):
"""
...
...
@@ -55,15 +55,16 @@ class Run(CLICmd):
help
=
"Instead of running the test only "
"list them and log their params."
)
parser
.
add_argument
(
'-z'
,
'--archive'
,
action
=
'store_true'
,
default
=
False
,
help
=
'Archive (ZIP) files generated by tests'
)
parser
.
add_argument
(
'-z'
,
'--archive'
,
action
=
'store_true'
,
default
=
False
,
help
=
'Archive (ZIP) files generated'
' by tests'
)
parser
.
add_argument
(
'--force-job-id'
,
dest
=
'unique_job_id'
,
type
=
str
,
default
=
None
,
help
=
(
'Forces the use of a particular job ID. Used '
'internally when interacting with an avocado '
'server. You should not use this option '
'unless you know exactly what you
\'
re doing'
)
)
help
=
'Forces the use of a particular job ID. Used '
'internally when interacting with an avocado '
'server. You should not use this option '
'unless you know exactly what you
\'
re doing'
)
parser
.
add_argument
(
'--job-results-dir'
,
action
=
'store'
,
dest
=
'logdir'
,
default
=
None
,
metavar
=
'DIRECTORY'
,
...
...
@@ -72,37 +73,37 @@ class Run(CLICmd):
parser
.
add_argument
(
'--job-timeout'
,
action
=
'store'
,
default
=
None
,
metavar
=
'SECONDS'
,
help
=
(
'Set the maximum amount of time (in SECONDS) that
'
'
tests are allowed to execute. '
'Values <= zero means "no timeout". '
'You can also use suffixes, like: '
' s (seconds), m (minutes), h (hours). '
)
)
help
=
'Set the maximum amount of time (in SECONDS)
'
'that
tests are allowed to execute. '
'Values <= zero means "no timeout". '
'You can also use suffixes, like: '
' s (seconds), m (minutes), h (hours). '
)
parser
.
add_argument
(
'--failfast'
,
choices
=
(
'on'
,
'off'
),
help
=
'Enable or disable the job interruption on '
'first failed test.'
)
'first failed test.'
)
sysinfo_default
=
settings
.
get_value
(
'sysinfo.collect'
,
'enabled'
,
key_type
=
'bool'
,
default
=
True
)
sysinfo_default
=
'on'
if
sysinfo_default
is
True
else
'off'
parser
.
add_argument
(
'--sysinfo'
,
choices
=
(
'on'
,
'off'
),
default
=
sysinfo_default
,
help
=
(
'Enable or disable system information '
'(hardware details, profilers, etc.). '
'Current: %(default)s'
)
)
parser
.
add_argument
(
'--sysinfo'
,
choices
=
(
'on'
,
'off'
),
default
=
sysinfo_default
,
help
=
"Enable or disable "
"system information (hardware details, profilers, "
"etc.). Current: %(default)s"
)
parser
.
output
=
parser
.
add_argument_group
(
'output and result format'
)
parser
.
output
.
add_argument
(
'-s'
,
'--silent'
,
action
=
"store_true"
,
default
=
argparse
.
SUPPRESS
,
help
=
'Silence stdout'
)
parser
.
output
.
add_argument
(
'-s'
,
'--silent'
,
action
=
"store_true"
,
default
=
argparse
.
SUPPRESS
,
help
=
'Silence stdout'
)
parser
.
output
.
add_argument
(
'--show-job-log'
,
action
=
'store_true'
,
default
=
False
,
help
=
(
'Display only the job log on stdout. Useful '
'for test debugging purposes. No output will '
'be displayed if you also specify --silent'
)
)
parser
.
output
.
add_argument
(
'--show-job-log'
,
action
=
'store_true'
,
default
=
False
,
help
=
"Display only the job "
"log on stdout. Useful for test debugging "
"purposes. No output will be displayed if "
"you also specify --silent"
)
parser
.
output
.
add_argument
(
"--store-logging-stream"
,
nargs
=
"*"
,
default
=
[],
metavar
=
"STREAM[:LEVEL]"
,
...
...
@@ -114,31 +115,24 @@ class Run(CLICmd):
out_check
.
add_argument
(
'--output-check-record'
,
choices
=
(
'none'
,
'all'
,
'stdout'
,
'stderr'
),
default
=
'none'
,
help
=
(
'Record output streams of your tests '
'to reference files (valid options: '
'none (do not record output streams), '
'all (record both stdout and stderr), '
'stdout (record only stderr), '
'stderr (record only stderr). '
'Current: %(default)s'
))
help
=
"Record output streams of your tests "
"to reference files (valid options: none (do "
"not record output streams), all (record both "
"stdout and stderr), stdout (record only "
"stderr), stderr (record only stderr). "
'Current: %(default)s'
)
out_check
.
add_argument
(
'--output-check'
,
choices
=
(
'on'
,
'off'
),
default
=
'on'
,
help
=
(
'Enable or disable test output (stdout/stderr) check. '
'If this option is off, no output will '
'be checked, even if there are reference files '
'present for the test. '
'Current: on (output check enabled)'
)
)
help
=
"Enable or disable test output (stdout/"
"stderr) check. If this option is off, no "
"output will be checked, even if there are "
"reference files present for the test. "
"Current: on (output check enabled)"
)
loader
.
add_loader_options
(
parser
)
mux
=
parser
.
add_argument_group
(
'test parameters'
)
if
multiplexer
.
MULTIPLEX_CAPABLE
:
mux
.
add_argument
(
'-m'
,
'--multiplex'
,
nargs
=
'*'
,
dest
=
'multiplex_files'
,
default
=
None
,
metavar
=
'FILE'
,
help
=
'Location of one or more Avocado multiplex '
'(.yaml) FILE(s) (order dependent)'
)
mux
.
add_argument
(
'--filter-only'
,
nargs
=
'*'
,
default
=
[],
help
=
'Filter only path(s) from multiplexing'
)
mux
.
add_argument
(
'--filter-out'
,
nargs
=
'*'
,
default
=
[],
...
...
@@ -150,19 +144,6 @@ class Run(CLICmd):
help
=
"Inject [path:]key:node values into the "
"final multiplex tree."
)
def
_activate
(
self
,
args
):
# Extend default multiplex tree of --mux_inject values
for
value
in
getattr
(
args
,
"mux_inject"
,
[]):
value
=
value
.
split
(
':'
,
2
)
if
len
(
value
)
<
2
:
raise
ValueError
(
"key:value pairs required, found only %s"
%
(
value
))
elif
len
(
value
)
==
2
:
args
.
default_avocado_params
.
value
[
value
[
0
]]
=
value
[
1
]
else
:
node
=
args
.
default_avocado_params
.
get_node
(
value
[
0
],
True
)
node
.
value
[
value
[
1
]]
=
value
[
2
]
def
run
(
self
,
args
):
"""
Run test modules or simple tests.
...
...
@@ -170,7 +151,6 @@ class Run(CLICmd):
:param args: Command line args received from the run subparser.
"""
log
=
logging
.
getLogger
(
"avocado.app"
)
self
.
_activate
(
args
)
if
args
.
unique_job_id
is
not
None
:
try
:
int
(
args
.
unique_job_id
,
16
)
...
...
@@ -188,9 +168,9 @@ class Run(CLICmd):
job_run
=
job_instance
.
run
()
result_dispatcher
=
ResultDispatcher
()
if
result_dispatcher
.
extensions
:
# At this point job_instance doesn't have a single results
attribute
#
which is the end goal. For now, we pick any of the plugin classes
# added to the result proxy.
# At this point job_instance doesn't have a single results
#
attribute which is the end goal. For now, we pick any of the
#
plugin classes
added to the result proxy.
if
len
(
job_instance
.
result_proxy
.
output_plugins
)
>
0
:
result
=
job_instance
.
result_proxy
.
output_plugins
[
0
]
result_dispatcher
.
map_method
(
'render'
,
result
,
job_instance
)
...
...
avocado/plugins/yaml_to_mux.py
0 → 100644
浏览文件 @
ac035ebb
# 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
# Author: Lukas Doktor <ldoktor@redhat.com>
"""Multiplexer plugin to parse yaml files to params"""
import
logging
import
os
import
re
import
sys
from
avocado.core
import
tree
,
exit_codes
from
avocado.core.plugin_interfaces
import
CLI
try
:
import
yaml
except
ImportError
:
MULTIPLEX_CAPABLE
=
False
else
:
MULTIPLEX_CAPABLE
=
True
try
:
from
yaml
import
CLoader
as
Loader
except
ImportError
:
from
yaml
import
Loader
# Mapping for yaml flags
YAML_INCLUDE
=
100
YAML_USING
=
101
YAML_REMOVE_NODE
=
tree
.
REMOVE_NODE
YAML_REMOVE_VALUE
=
tree
.
REMOVE_VALUE
YAML_MUX
=
102
__RE_FILE_SPLIT
=
re
.
compile
(
r
'(?<!\\):'
)
# split by ':' but not '\\:'
__RE_FILE_SUBS
=
re
.
compile
(
r
'(?<!\\)\\:'
)
# substitute '\\:' but not '\\\\:'
class
Value
(
tuple
):
# Few methods pylint: disable=R0903
""" Used to mark values to simplify checking for node vs. value """
pass
class
ListOfNodeObjects
(
list
):
# Few methods pylint: disable=R0903
"""
Used to mark list as list of objects from whose node is going to be created
"""
pass
def
_create_from_yaml
(
path
,
cls_node
=
tree
.
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
.
TreeNode
):
node
.
add_child
(
value
)
elif
isinstance
(
value
[
0
],
tree
.
Control
):
if
value
[
0
].
code
==
YAML_INCLUDE
:
# Include file
ypath
=
value
[
1
]
if
not
os
.
path
.
isabs
(
ypath
):
ypath
=
os
.
path
.
join
(
os
.
path
.
dirname
(
path
),
ypath
)
if
not
os
.
path
.
exists
(
ypath
):
raise
ValueError
(
"File '%s' included from '%s' does not "
"exist."
%
(
ypath
,
path
))
node
.
merge
(
_create_from_yaml
(
'/:'
+
ypath
,
cls_node
))
elif
value
[
0
].
code
==
YAML_USING
:
if
using
:
raise
ValueError
(
"!using can be used only once per "
"node! (%s:%s)"
%
(
path
,
name
))
using
=
value
[
1
]
if
using
[
0
]
==
'/'
:
using
=
using
[
1
:]
if
using
[
-
1
]
==
'/'
:
using
=
using
[:
-
1
]
elif
value
[
0
].
code
==
YAML_REMOVE_NODE
:
value
[
0
].
value
=
value
[
1
]
# set the name
node
.
ctrl
.
append
(
value
[
0
])
# add "blue pill" of death
elif
value
[
0
].
code
==
YAML_REMOVE_VALUE
:
value
[
0
].
value
=
value
[
1
]
# set the name
node
.
ctrl
.
append
(
value
[
0
])
elif
value
[
0
].
code
==
YAML_MUX
:
node
.
multiplex
=
True
else
:
node
.
value
[
value
[
0
]]
=
value
[
1
]
if
using
:
if
name
is
not
''
:
for
name
in
using
.
split
(
'/'
)[::
-
1
]:
node
=
cls_node
(
name
,
children
=
[
node
])
else
:
using
=
using
.
split
(
'/'
)[::
-
1
]
node
.
name
=
using
.
pop
()
while
True
:
if
not
using
:
break
name
=
using
.
pop
()
# 'using' is list pylint: disable=E1101
node
=
cls_node
(
name
,
children
=
[
node
])
node
=
cls_node
(
''
,
children
=
[
node
])
return
node
def
mapping_to_tree_loader
(
loader
,
node
):
""" Maps yaml mapping tag to TreeNode structure """
_value
=
[]
for
key_node
,
value_node
in
node
.
value
:
if
key_node
.
tag
.
startswith
(
'!'
):
# reflect tags everywhere
key
=
loader
.
construct_object
(
key_node
)
else
:
key
=
loader
.
construct_python_str
(
key_node
)
value
=
loader
.
construct_object
(
value_node
)
_value
.
append
((
key
,
value
))
objects
=
ListOfNodeObjects
()
for
name
,
values
in
_value
:
if
isinstance
(
values
,
ListOfNodeObjects
):
# New node from list
objects
.
append
(
tree_node_from_values
(
name
,
values
))
elif
values
is
None
:
# Empty node
objects
.
append
(
cls_node
(
str
(
name
)))
else
:
# Values
objects
.
append
(
Value
((
name
,
values
)))
return
objects
def
mux_loader
(
loader
,
obj
):
"""
Special !mux loader which allows to tag node as 'multiplex = True'.
"""
if
not
isinstance
(
obj
,
yaml
.
ScalarNode
):
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
))
return
objects
Loader
.
add_constructor
(
u
'!include'
,
lambda
loader
,
node
:
tree
.
Control
(
YAML_INCLUDE
))
Loader
.
add_constructor
(
u
'!using'
,
lambda
loader
,
node
:
tree
.
Control
(
YAML_USING
))
Loader
.
add_constructor
(
u
'!remove_node'
,
lambda
loader
,
node
:
tree
.
Control
(
YAML_REMOVE_NODE
))
Loader
.
add_constructor
(
u
'!remove_value'
,
lambda
loader
,
node
:
tree
.
Control
(
YAML_REMOVE_VALUE
))
Loader
.
add_constructor
(
u
'!mux'
,
mux_loader
)
Loader
.
add_constructor
(
yaml
.
resolver
.
BaseResolver
.
DEFAULT_MAPPING_TAG
,
mapping_to_tree_loader
)
# Parse file name ([$using:]$path)
path
=
__RE_FILE_SPLIT
.
split
(
path
,
1
)
if
len
(
path
)
==
1
:
path
=
__RE_FILE_SUBS
.
sub
(
':'
,
path
[
0
])
using
=
[
"run"
]
else
:
nodes
=
__RE_FILE_SUBS
.
sub
(
':'
,
path
[
0
]).
strip
(
'/'
).
split
(
'/'
)
using
=
[
node
for
node
in
nodes
if
node
]
if
not
path
[
0
].
startswith
(
'/'
):
# relative path, put into /run
using
.
insert
(
0
,
'run'
)
path
=
__RE_FILE_SUBS
.
sub
(
':'
,
path
[
1
])
# Load the tree
with
open
(
path
)
as
stream
:
loaded_tree
=
yaml
.
load
(
stream
,
Loader
)
if
loaded_tree
is
None
:
return
loaded_tree
=
tree_node_from_values
(
''
,
loaded_tree
)
# Add prefix
if
using
:
loaded_tree
.
name
=
using
.
pop
()
while
True
:
if
not
using
:
break
loaded_tree
=
cls_node
(
using
.
pop
(),
children
=
[
loaded_tree
])
loaded_tree
=
cls_node
(
''
,
children
=
[
loaded_tree
])
return
loaded_tree
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 """
tmp
=
_create_from_yaml
(
path
)
if
tmp
:
data
.
merge
(
tmp
)
def
_merge_debug
(
data
,
path
):
""" Use NamedTreeNodeDebug magic """
node_cls
=
tree
.
get_named_tree_cls
(
path
)
tmp
=
_create_from_yaml
(
path
,
node_cls
)
if
tmp
:
data
.
merge
(
tmp
)
if
not
debug
:
data
=
tree
.
TreeNode
()
merge
=
_merge
else
:
data
=
tree
.
TreeNodeDebug
()
merge
=
_merge_debug
path
=
None
try
:
for
path
in
paths
:
merge
(
data
,
path
)
# Yaml can raise IndexError on some files
except
(
yaml
.
YAMLError
,
IndexError
)
as
details
:
if
'mapping values are not allowed in this context'
in
str
(
details
):
details
=
(
"%s
\n
Make sure !tags and colons are separated by a "
"space (eg. !include :)"
%
details
)
msg
=
"Invalid multiplex file '%s': %s"
%
(
path
,
details
)
raise
IOError
(
2
,
msg
,
path
)
return
data
class
YamlToMux
(
CLI
):
"""
Registers callback to inject params from yaml file to the
"""
name
=
'yaml_to_mux'
description
=
"YamlToMux options for the 'run' subcommand"
def
configure
(
self
,
parser
):
"""
Configures "run" and "multiplex" subparsers
"""
if
not
MULTIPLEX_CAPABLE
:
return
for
name
in
(
"run"
,
"multiplex"
):
subparser
=
parser
.
subcommands
.
choices
.
get
(
name
,
None
)
if
subparser
is
None
:
continue
mux
=
subparser
.
add_argument_group
(
"yaml to mux options"
)
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
(
"--multiplex"
,
nargs
=
'*'
,
default
=
None
,
metavar
=
"FILE"
,
help
=
"DEPRECATED: Location of one or more Avocado"
" multiplex (.yaml) FILE(s) (order dependent)"
)
def
run
(
self
,
args
):
# 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
))
except
IOError
as
details
:
logging
.
getLogger
(
"avocado.app"
).
error
(
details
.
strerror
)
sys
.
exit
(
exit_codes
.
AVOCADO_JOB_FAIL
)
# Deprecated --multiplex option
multiplex_files
=
getattr
(
args
,
"multiplex"
,
None
)
if
multiplex_files
:
msg
=
(
"The use of `--multiplex` is deprecated, use `--mux-yaml` "
"instead."
)
logging
.
getLogger
(
"avocado.test"
).
warning
(
msg
)
debug
=
getattr
(
args
,
"mux_debug"
,
False
)
try
:
args
.
mux
.
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
)
docs/source/DebuggingWithGDB.rst
浏览文件 @
ac035ebb
...
...
@@ -69,7 +69,7 @@ place, the test notifies you and you can investigate the problem. This is
demonstrated in ``examples/tests/doublefree_nasty.py`` test. To unveil the
power of Avocado, run this test using::
avocado run --gdb-run-bin=doublefree: examples/tests/doublefree_nasty.py --gdb-prerun-commands examples/tests/doublefree_nasty.py.data/gdb_pre --mu
ltiplex
examples/tests/doublefree_nasty.py.data/iterations.yaml
avocado run --gdb-run-bin=doublefree: examples/tests/doublefree_nasty.py --gdb-prerun-commands examples/tests/doublefree_nasty.py.data/gdb_pre --mu
x-yaml
examples/tests/doublefree_nasty.py.data/iterations.yaml
which executes 100 iterations of this test while setting all breakpoints from
the ``examples/tests/doublefree_nasty.py.data/gdb_pre`` file (you can specify
...
...
docs/source/Mu
ltiplexConfig
.rst
→
docs/source/Mu
x
.rst
浏览文件 @
ac035ebb
.. _mu
ltiplex_configuration
:
.. _mu
x
:
=======================
Multiplex Configuration
=======================
===================
Test variants - Mux
===================
The ``Mux`` 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
manner define test matrices with all possible variants.
This sounds similar to sparse matrix jobs in Jenkins, but the difference is
that instead of filters, which are available too, avocado allows specifying
so called ``mux domains``, which is a nicer way to represent data.
As the data is represented in trees it creates all possible variants
per domain and then all combinations of these. It sounds complicated, but
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.
Mux internals
-------------
The ``Mux`` 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``,
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::
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
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
database (see `yaml_to_mux plugin`_)
3. Custom plugin using the simple ``Mux`` API (see `mux_api`_)
.. _mux_api:
Mux 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:
1. Initialize ``Mux`` in ``args.mux``
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
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
* 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
into the database.
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`_
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``
to generate multiple variants of the same test with
different values. To define these variants and values
term ``Multiplexation``
or ``Mux`` 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
are still human readable, unlike traditional sparse, multi-dimensional-matrices
...
...
@@ -48,7 +135,7 @@ flags (lines 2, 9, 14, 19) which modifies the behavior.
Nodes
=====
-----
They define context of the key=>value pairs allowing us to easily identify
for what this values might be used for and also it makes possible to define
...
...
@@ -59,7 +146,7 @@ is disabled, so the value of node name is always as written in the yaml
file (unlike values, where `yes` converts to `True` and such).
Nodes are organized in parent-child relationship and together they create
a tree. To view this structure use ``avocado multiplex --tree <file>``::
a tree. To view this structure use ``avocado multiplex --tree
-m
<file>``::
┗━━ run
┣━━ hw
...
...
@@ -85,7 +172,7 @@ parameters available in tests.
Keys and Values
===============
---------------
Every value other than dict (4,6,8,11) is used as value of the antecedent
node.
...
...
@@ -127,7 +214,7 @@ This means that the value can be of any YAML supported value, eg. bool, None,
list or custom type, while the key is always string.
Variants
========
--------
In the end all leaves are gathered and turned into parameters, more specifically into
``AvocadoParams``::
...
...
@@ -182,7 +269,7 @@ Results in::
Resolution order
================
----------------
You can see that only leaves are part of the test parameters. It might happen
that some of these leaves contain different values of the same key. Then
...
...
@@ -225,11 +312,11 @@ relative paths (the ones starting with ``*``)
Injecting files
===============
---------------
You can run any test with any YAML file by::
avocado run sleeptest.py --mu
ltiplex
file.yaml
avocado run sleeptest.py --mu
x-yaml
file.yaml
This puts the content of ``file.yaml`` into ``/run``
location, which as mentioned in previous section, is the default ``mux-path``
...
...
@@ -241,7 +328,7 @@ when you have two files and you don't want the content to be merged into
a single place becomming effectively a single blob, you can do that by
giving a name to your yaml file::
avocado run sleeptest.py --mu
ltiplex
duration:duration.yaml
avocado run sleeptest.py --mu
x-yaml
duration:duration.yaml
The content of ``duration.yaml`` is injected into ``/run/duration``. Still when
keys from other files don't clash, you can use ``params.get(key)`` and retrieve
...
...
@@ -253,7 +340,7 @@ multiple files by using the same or different name, or even a complex
Last but not least, advanced users can inject the file into whatever location
they prefer by::
avocado run sleeptest.py --mu
ltiplex
/my/variants/duration:duration.yaml
avocado run sleeptest.py --mu
x-yaml
/my/variants/duration:duration.yaml
Simple ``params.get(key)`` won't look in this location, which might be the
intention of the test writer. There are several ways to access the values:
...
...
@@ -272,7 +359,7 @@ parameters.
Multiple files
==============
--------------
You can provide multiple files. In such scenario final tree is a combination
of the provided files where later nodes with the same name override values of
...
...
@@ -318,7 +405,7 @@ Whole file is **merged** into the node where it's defined.
Advanced YAML tags
==================
------------------
There are additional features related to YAML files. Most of them require values
separated by ``":"``. Again, in all such cases it's mandatory to add a white space
...
...
@@ -391,7 +478,7 @@ child, etc. Example is in section `Variants`_
Complete example
================
----------------
Let's take a second look at the first example::
...
...
@@ -422,7 +509,7 @@ Let's take a second look at the first example::
After filters are applied (simply removes non-matching variants), leaves
are gathered and all variants are generated::
$ avocado multiplex examples/mux-environment.yaml
$ avocado multiplex
-m
examples/mux-environment.yaml
Variants generated:
Variant 1: /hw/cpu/intel, /hw/disk/scsi, /distro/fedora, /env/debug
Variant 2: /hw/cpu/intel, /hw/disk/scsi, /distro/fedora, /env/prod
...
...
docs/source/Replay.rst
浏览文件 @
ac035ebb
...
...
@@ -40,7 +40,7 @@ The replay feature will retrieve the original job urls, the multiplex
tree and the configuration. Let's see another example, now using
multiplex file::
$ avocado run /bin/true /bin/false --mu
ltiplex
mux-environment.yaml
$ avocado run /bin/true /bin/false --mu
x-yaml
mux-environment.yaml
JOB ID : bd6aa3b852d4290637b5e771b371537541043d1d
JOB LOG : $HOME/avocado/job-results/job-2016-01-11T21.56-bd6aa3b/job.log
TESTS : 48
...
...
docs/source/WritingTests.rst
浏览文件 @
ac035ebb
...
...
@@ -55,7 +55,7 @@ Note that the test class provides you with a number of convenience attributes:
of ``self.log``. It lets you log debug, info, error and warning messages.
* A parameter passing system (and fetching system) that can be accessed by
means of ``self.params``. This is hooked to the Multiplexer, about which
you can find that more information at :doc:`Mu
ltiplexConfig
`.
you can find that more information at :doc:`Mu
x
`.
Saving test generated (custom) data
===================================
...
...
@@ -88,7 +88,7 @@ Accessing test parameters
Each test has a set of parameters that can be accessed through
``self.params.get($name, $path=None, $default=None)``.
Avocado finds and populates ``self.params`` with all parameters you define on
a Multiplex Config file (see :doc:`Mu
ltiplexConfig
`). As an example, consider
a Multiplex Config file (see :doc:`Mu
x
`). As an example, consider
the following multiplex file for sleeptest::
sleeptest:
...
...
@@ -101,9 +101,9 @@ the following multiplex file for sleeptest::
long:
sleep_length: 5
When running this example by ``avocado run $test --mu
ltiplex
$file.yaml``
When running this example by ``avocado run $test --mu
x-yaml
$file.yaml``
three variants are executed and the content is injected into ``/run`` namespace
(see :doc:`Mu
ltiplexConfig
` for details). Every variant contains variables
(see :doc:`Mu
x
` for details). Every variant contains variables
"type" and "sleep_length". To obtain the current value, you need the name
("sleep_length") and its path. The path differs for each variant so it's
needed to use the most suitable portion of the path, in this example:
...
...
@@ -146,7 +146,7 @@ simply inject the values elsewhere (eg. `/run/sleeptest` =>
default path, which won't generate clash, but would return their values
instead. Then you need to clarify the path (eg. `'*'` => `sleeptest/*`)
More details on that are in :doc:`Mu
ltiplexConfig
`
More details on that are in :doc:`Mu
x
`
Using a multiplex file
======================
...
...
@@ -154,7 +154,7 @@ Using a multiplex file
You may use the Avocado runner with a multiplex file to provide params and matrix
generation for sleeptest just like::
$ avocado run sleeptest.py --mu
ltiplex
examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado run sleeptest.py --mu
x-yaml
examples/tests/sleeptest.py.data/sleeptest.yaml
JOB ID : d565e8dec576d6040f894841f32a836c751f968f
JOB LOG : $HOME/avocado/job-results/job-2014-08-12T15.44-d565e8de/job.log
TESTS : 3
...
...
@@ -165,8 +165,8 @@ generation for sleeptest just like::
TESTS TIME : 6.50 s
JOB HTML : $HOME/avocado/job-results/job-2014-08-12T15.44-d565e8de/html/results.html
The ``--mu
ltiplex
`` accepts either only ``$FILE_LOCATION`` or ``$INJECT_TO:$FILE_LOCATION``.
As explained in :doc:`Mu
ltiplexConfig
` without any path the content gets
The ``--mu
x-yaml
`` accepts either only ``$FILE_LOCATION`` or ``$INJECT_TO:$FILE_LOCATION``.
As explained in :doc:`Mu
x
` without any path the content gets
injected into ``/run`` in order to be in the default relative path location.
The ``$INJECT_TO`` can be either relative path, then it's injected into
``/run/$INJECT_TO`` location, or absolute path (starting with ``'/'``), then
...
...
@@ -174,19 +174,19 @@ it's injected directly into the specified path and it's up to the test/framework
developer to get the value from this location (using path or adding the path to
``mux-path``). To understand the difference execute those commands::
$ avocado multiplex -t examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado multiplex -t duration:examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado multiplex -t /my/location:examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado multiplex -t
-m
examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado multiplex -t
-m
duration:examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado multiplex -t
-m
/my/location:examples/tests/sleeptest.py.data/sleeptest.yaml
Note that, as your multiplex file specifies all parameters for sleeptest, you
can't leave the test ID empty::
$ scripts/avocado run --mu
ltiplex
examples/tests/sleeptest/sleeptest.yaml
$ scripts/avocado run --mu
x-yaml
examples/tests/sleeptest/sleeptest.yaml
Empty test ID. A test path or alias must be provided
You can also execute multiple tests with the same multiplex file::
$ avocado run sleeptest.py synctest.py --mu
ltiplex
examples/tests/sleeptest.py.data/sleeptest.yaml
$ avocado run sleeptest.py synctest.py --mu
x-yaml
examples/tests/sleeptest.py.data/sleeptest.yaml
JOB ID : cd20fc8d1714da6d4791c19322374686da68c45c
JOB LOG : $HOME/avocado/job-results/job-2016-05-04T09.25-cd20fc8/job.log
TESTS : 8
...
...
@@ -775,7 +775,7 @@ impact your test grid. You can account for that possibility and set up a
::
$ avocado run sleeptest.py --mu
ltiplex
/tmp/sleeptest-example.yaml
$ avocado run sleeptest.py --mu
x-yaml
/tmp/sleeptest-example.yaml
JOB ID : 6d5a2ff16bb92395100fbc3945b8d253308728c9
JOB LOG : $HOME/avocado/job-results/job-2014-08-12T15.52-6d5a2ff1/job.log
TESTS : 1
...
...
@@ -1099,7 +1099,7 @@ Here are the current variables that Avocado exports to the tests:
+-------------------------+---------------------------------------+-----------------------------------------------------------------------------------------------------+
| AVOCADO_TEST_SYSINFODIR | The system information directory | $HOME/logs/job-results/job-2014-09-16T14.38-ac332e6/test-results/$HOME/my_test.sh.1/sysinfo |
+-------------------------+---------------------------------------+-----------------------------------------------------------------------------------------------------+
| * | All variables from --mu
ltiplex-file
| TIMEOUT=60; IO_WORKERS=10; VM_BYTES=512M; ... |
| * | All variables from --mu
x-yaml
| TIMEOUT=60; IO_WORKERS=10; VM_BYTES=512M; ... |
+-------------------------+---------------------------------------+-----------------------------------------------------------------------------------------------------+
...
...
docs/source/index.rst
浏览文件 @
ac035ebb
...
...
@@ -14,7 +14,7 @@ Contents:
Configuration
Loaders
LoggingSystem
Mu
ltiplexConfig
Mu
x
Replay
Diff
RunningTestsRemotely
...
...
selftests/.data/empty_file
0 → 100644
浏览文件 @
ac035ebb
selftests/functional/test_multiplex.py
浏览文件 @
ac035ebb
...
...
@@ -34,27 +34,32 @@ class MultiplexTests(unittest.TestCase):
def
setUp
(
self
):
self
.
tmpdir
=
tempfile
.
mkdtemp
(
prefix
=
'avocado_'
+
__name__
)
def
run_and_check
(
self
,
cmd_line
,
expected_rc
):
def
run_and_check
(
self
,
cmd_line
,
expected_rc
,
tests
=
None
):
os
.
chdir
(
basedir
)
result
=
process
.
run
(
cmd_line
,
ignore_status
=
True
)
self
.
assertEqual
(
result
.
exit_status
,
expected_rc
,
"Command %s did not return rc "
"%d:
\n
%s"
%
(
cmd_line
,
expected_rc
,
result
))
if
tests
:
exp
=
(
"PASS %s | ERROR 0 | FAIL %s | SKIP 0 | WARN 0 | "
"INTERRUPT 0"
%
tests
)
self
.
assertIn
(
exp
,
result
.
stdout
,
"%s not in stdout:
\n
%s"
%
(
exp
,
result
))
return
result
def
test_mplex_plugin
(
self
):
cmd_line
=
'./scripts/avocado multiplex examples/tests/sleeptest.py.data/sleeptest.yaml'
cmd_line
=
'./scripts/avocado multiplex
-m
examples/tests/sleeptest.py.data/sleeptest.yaml'
expected_rc
=
exit_codes
.
AVOCADO_ALL_OK
self
.
run_and_check
(
cmd_line
,
expected_rc
)
def
test_mplex_plugin_nonexistent
(
self
):
cmd_line
=
'./scripts/avocado multiplex nonexist'
cmd_line
=
'./scripts/avocado multiplex
-m
nonexist'
expected_rc
=
exit_codes
.
AVOCADO_JOB_FAIL
result
=
self
.
run_and_check
(
cmd_line
,
expected_rc
)
self
.
assertIn
(
'No such file or directory'
,
result
.
stderr
)
def
test_mplex_debug
(
self
):
cmd_line
=
(
'./scripts/avocado multiplex -c -d '
cmd_line
=
(
'./scripts/avocado multiplex -c -d
-m
'
'/:examples/mux-selftest.yaml '
'/:examples/mux-environment.yaml '
'/:examples/mux-selftest.yaml '
...
...
@@ -65,41 +70,47 @@ class MultiplexTests(unittest.TestCase):
def
test_run_mplex_noid
(
self
):
cmd_line
=
(
'./scripts/avocado run --job-results-dir %s --sysinfo=off '
'-
-multiplex
examples/tests/sleeptest.py.data/sleeptest.yaml'
%
self
.
tmpdir
)
'-
m
examples/tests/sleeptest.py.data/sleeptest.yaml'
%
self
.
tmpdir
)
expected_rc
=
exit_codes
.
AVOCADO_JOB_FAIL
self
.
run_and_check
(
cmd_line
,
expected_rc
)
def
test_run_mplex_passtest
(
self
):
cmd_line
=
(
'./scripts/avocado run --job-results-dir %s --sysinfo=off '
'passtest.py -
-multiplex
'
'passtest.py -
m
'
'examples/tests/sleeptest.py.data/sleeptest.yaml'
%
self
.
tmpdir
)
expected_rc
=
exit_codes
.
AVOCADO_ALL_OK
self
.
run_and_check
(
cmd_line
,
expected_rc
)
self
.
run_and_check
(
cmd_line
,
expected_rc
,
(
4
,
0
)
)
def
test_run_mplex_doublepass
(
self
):
cmd_line
=
(
'./scripts/avocado run --job-results-dir %s --sysinfo=off '
'passtest.py passtest.py -
-multiplex
'
'passtest.py passtest.py -
m
'
'examples/tests/sleeptest.py.data/sleeptest.yaml'
%
self
.
tmpdir
)
self
.
run_and_check
(
cmd_line
,
ex
pected_rc
=
0
)
self
.
run_and_check
(
cmd_line
,
ex
it_codes
.
AVOCADO_ALL_OK
,
(
8
,
0
)
)
def
test_run_mplex_failtest
(
self
):
cmd_line
=
(
'./scripts/avocado run --job-results-dir %s --sysinfo=off '
'passtest.py failtest.py -
-multiplex
'
'passtest.py failtest.py -
m
'
'examples/tests/sleeptest.py.data/sleeptest.yaml'
%
self
.
tmpdir
)
expected_rc
=
exit_codes
.
AVOCADO_TESTS_FAIL
self
.
run_and_check
(
cmd_line
,
expected_rc
)
self
.
run_and_check
(
cmd_line
,
expected_rc
,
(
4
,
4
)
)
def
test_run_double_mplex
(
self
):
cmd_line
=
(
'./scripts/avocado run --job-results-dir %s --sysinfo=off '
'passtest.py -
-multiplex
'
'passtest.py -
m
'
'examples/tests/sleeptest.py.data/sleeptest.yaml '
'examples/tests/sleeptest.py.data/sleeptest.yaml'
%
self
.
tmpdir
)
expected_rc
=
exit_codes
.
AVOCADO_ALL_OK
self
.
run_and_check
(
cmd_line
,
expected_rc
)
self
.
run_and_check
(
cmd_line
,
expected_rc
,
(
4
,
0
))
def
test_empty_file
(
self
):
cmd_line
=
(
"./scripts/avocado run -m selftests/.data/empty_file "
"-- passtest.py"
)
result
=
self
.
run_and_check
(
cmd_line
,
exit_codes
.
AVOCADO_ALL_OK
,
(
1
,
0
))
def
test_run_mplex_params
(
self
):
for
variant_msg
in
((
'/run/short'
,
'A'
),
...
...
@@ -107,7 +118,7 @@ class MultiplexTests(unittest.TestCase):
(
'/run/long'
,
'This is very long
\n
multiline
\n
text.'
)):
variant
,
msg
=
variant_msg
cmd_line
=
(
'./scripts/avocado run --job-results-dir %s --sysinfo=off examples/tests/env_variables.sh '
'-
-multiplex
examples/tests/env_variables.sh.data/env_variables.yaml '
'-
m
examples/tests/env_variables.sh.data/env_variables.yaml '
'--filter-only %s --show-job-log'
%
(
self
.
tmpdir
,
variant
))
expected_rc
=
exit_codes
.
AVOCADO_ALL_OK
result
=
self
.
run_and_check
(
cmd_line
,
expected_rc
)
...
...
selftests/functional/test_replay_basic.py
浏览文件 @
ac035ebb
...
...
@@ -24,8 +24,7 @@ class ReplayTests(unittest.TestCase):
def
setUp
(
self
):
self
.
tmpdir
=
tempfile
.
mkdtemp
(
prefix
=
'avocado_'
+
__name__
)
cmd_line
=
(
'./scripts/avocado run passtest.py '
'--multiplex '
'examples/tests/sleeptest.py.data/sleeptest.yaml '
'-m examples/tests/sleeptest.py.data/sleeptest.yaml '
'--job-results-dir %s --sysinfo=off --json -'
%
self
.
tmpdir
)
expected_rc
=
exit_codes
.
AVOCADO_ALL_OK
...
...
@@ -170,14 +169,14 @@ class ReplayTests(unittest.TestCase):
"""
Runs a replay job with custom a mux and using '--replay-test-status'
"""
cmd_line
=
(
'./scripts/avocado run --replay %s --
multiple
x '
'
examples/mux-environment.yaml
--replay-test-status FAIL '
cmd_line
=
(
'./scripts/avocado run --replay %s --
replay-ignore mu
x '
'--replay-test-status FAIL '
'--job-results-dir %s --replay-data-dir %s '
'--sysinfo=off'
%
(
self
.
jobid
,
self
.
tmpdir
,
self
.
jobdir
))
expected_rc
=
exit_codes
.
AVOCADO_FAIL
result
=
self
.
run_and_check
(
cmd_line
,
expected_rc
)
msg
=
"Option --replay-test-status is incompatible with "
\
"--multiplex."
msg
=
(
"Option `--replay-test-status` is incompatible with "
"`--replay-ignore mux`"
)
self
.
assertIn
(
msg
,
result
.
stderr
)
def
test_run_replay_status_and_urls
(
self
):
...
...
selftests/functional/test_replay_external_runner.py
浏览文件 @
ac035ebb
...
...
@@ -26,8 +26,7 @@ class ReplayExtRunnerTests(unittest.TestCase):
self
.
tmpdir
=
tempfile
.
mkdtemp
(
prefix
=
'avocado_'
+
__name__
)
test
=
script
.
make_script
(
os
.
path
.
join
(
self
.
tmpdir
,
'test'
),
'exit 0'
)
cmd_line
=
(
'./scripts/avocado run %s '
'--multiplex '
'examples/tests/sleeptest.py.data/sleeptest.yaml '
'-m examples/tests/sleeptest.py.data/sleeptest.yaml '
'--external-runner /bin/bash '
'--job-results-dir %s --sysinfo=off --json -'
%
(
test
,
self
.
tmpdir
))
...
...
selftests/unit/test_multiplexer.py
浏览文件 @
ac035ebb
...
...
@@ -4,6 +4,7 @@ import sys
from
avocado.core
import
multiplexer
from
avocado.core
import
tree
from
avocado.plugins
import
yaml_to_mux
if
sys
.
version_info
[:
2
]
==
(
2
,
6
):
import
unittest2
as
unittest
...
...
@@ -24,54 +25,48 @@ def combine(leaves_pools):
class
TestMultiplex
(
unittest
.
TestCase
):
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
setUp
(
self
):
self
.
mux_tree
=
tree
.
create_from_yaml
([
'/:'
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
])
self
.
mux_tree
=
yaml_to_mux
.
create_from_yaml
([
'/:'
+
PATH_PREFIX
+
'examples/mux-selftest.'
'yaml'
])
self
.
mux_full
=
tuple
(
multiplexer
.
MuxTree
(
self
.
mux_tree
))
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_empty
(
self
):
act
=
tuple
(
multiplexer
.
MuxTree
(
tree
.
TreeNode
()))
self
.
assertEqual
(
act
,
([
''
,
],))
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_partial
(
self
):
exp
=
([
'intel'
,
'scsi'
],
[
'intel'
,
'virtio'
],
[
'amd'
,
'scsi'
],
[
'amd'
,
'virtio'
],
[
'arm'
,
'scsi'
],
[
'arm'
,
'virtio'
])
act
=
tuple
(
multiplexer
.
MuxTree
(
self
.
mux_tree
.
children
[
0
]))
self
.
assertEqual
(
act
,
exp
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_full
(
self
):
self
.
assertEqual
(
len
(
self
.
mux_full
),
12
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_create_variants
(
self
):
from_file
=
multiplexer
.
yaml2tree
(
from_file
=
yaml_to_mux
.
create_from_yaml
(
[
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
])
from_file
=
multiplexer
.
MuxTree
(
from_file
)
self
.
assertEqual
(
self
.
mux_full
,
tuple
(
from_file
))
# Filters are tested in tree_unittests, only verify `multiplex_yamls` calls
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_filter_only
(
self
):
exp
=
([
'intel'
,
'scsi'
],
[
'intel'
,
'virtio'
])
act
=
multiplexer
.
yaml2tree
([
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
],
(
'/hw/cpu/intel'
,
'/distro/fedora'
,
'/hw'
))
act
=
yaml_to_mux
.
create_from_yaml
([
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
])
act
=
tree
.
apply_filters
(
act
,
(
'/hw/cpu/intel'
,
'/distro/fedora'
,
'/hw'
))
act
=
tuple
(
multiplexer
.
MuxTree
(
act
))
self
.
assertEqual
(
act
,
exp
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_filter_out
(
self
):
act
=
multiplexer
.
yaml2tree
([
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
],
None
,
(
'/hw/cpu/intel'
,
'/distro/fedora'
,
'/distro'
))
act
=
yaml_to_mux
.
create_from_yaml
([
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
])
act
=
tree
.
apply_filters
(
act
,
None
,
(
'/hw/cpu/intel'
,
'/distro/fedora'
,
'/distro'
))
act
=
tuple
(
multiplexer
.
MuxTree
(
act
))
self
.
assertEqual
(
len
(
act
),
4
)
self
.
assertEqual
(
len
(
act
[
0
]),
3
)
...
...
@@ -85,8 +80,8 @@ class TestMultiplex(unittest.TestCase):
class
TestAvocadoParams
(
unittest
.
TestCase
):
def
setUp
(
self
):
yamls
=
multiplexer
.
yaml2tree
([
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest-params.yaml'
])
yamls
=
yaml_to_mux
.
create_from_yaml
([
"/:"
+
PATH_PREFIX
+
'examples/mux-selftest-params.yaml'
])
self
.
yamls
=
iter
(
multiplexer
.
MuxTree
(
yamls
))
self
.
params1
=
multiplexer
.
AvocadoParams
(
self
.
yamls
.
next
(),
'Unittest1'
,
[
'/ch0/*'
,
'/ch1/*'
],
{})
...
...
@@ -95,13 +90,13 @@ class TestAvocadoParams(unittest.TestCase):
self
.
params2
=
multiplexer
.
AvocadoParams
(
self
.
yamls
.
next
(),
'Unittest2'
,
[
'/ch1/*'
,
'/ch0/*'
],
{})
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_pickle
(
self
):
params
=
pickle
.
dumps
(
self
.
params1
,
2
)
# protocol == 2
params
=
pickle
.
loads
(
params
)
self
.
assertEqual
(
self
.
params1
,
params
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_basic
(
self
):
self
.
assertEqual
(
self
.
params1
,
self
.
params1
)
self
.
assertNotEqual
(
self
.
params1
,
self
.
params2
)
...
...
@@ -110,7 +105,7 @@ class TestAvocadoParams(unittest.TestCase):
str
(
multiplexer
.
AvocadoParams
([],
'Unittest'
,
[],
{}))
self
.
assertEqual
(
15
,
sum
([
1
for
_
in
self
.
params1
.
iteritems
()]))
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_unhashable
(
self
):
""" Verifies that unhashable arguments can be passed to params.get """
self
.
assertEqual
(
self
.
params1
.
get
(
"root"
,
"/ch0/"
,
[
"foo"
]),
[
"foo"
])
...
...
@@ -118,7 +113,7 @@ class TestAvocadoParams(unittest.TestCase):
'/ch0/ch0.1/ch0.1.1/ch0.1.1.1/'
,
[
'bar'
]),
'unique1'
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_get_abs_path
(
self
):
# /ch0/ is not leaf thus it's not queryable
self
.
assertEqual
(
self
.
params1
.
get
(
'root'
,
'/ch0/'
,
'bbb'
),
'bbb'
)
...
...
@@ -140,7 +135,7 @@ class TestAvocadoParams(unittest.TestCase):
'/ch0/ch0.1/ch0.1.1/ch0.1.1.1/'
,
'hhh'
),
'hhh'
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_get_greedy_path
(
self
):
self
.
assertEqual
(
self
.
params1
.
get
(
'unique1'
,
'/*/*/*/ch0.1.1.1/'
,
111
),
'unique1'
)
...
...
@@ -164,7 +159,7 @@ class TestAvocadoParams(unittest.TestCase):
# path matches nothing
self
.
assertEqual
(
self
.
params1
.
get
(
'root'
,
''
,
999
),
999
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_get_rel_path
(
self
):
self
.
assertEqual
(
self
.
params1
.
get
(
'root'
,
default
=
'iii'
),
'root'
)
self
.
assertEqual
(
self
.
params1
.
get
(
'unique1'
,
'*'
,
'jjj'
),
'unique1'
)
...
...
@@ -179,7 +174,7 @@ class TestAvocadoParams(unittest.TestCase):
self
.
assertEqual
(
self
.
params2
.
get
(
'unique1'
,
'*/ch0.1.1.1/'
,
'ooo'
),
'ooo'
)
@
unittest
.
skipIf
(
not
tree
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
@
unittest
.
skipIf
(
not
yaml_to_mux
.
MULTIPLEX_CAPABLE
,
"Not multiplex capable"
)
def
test_get_clashes
(
self
):
# One inherited, the other is new
self
.
assertRaisesRegexp
(
ValueError
,
r
"'clash1'.* \['/ch0/ch0.1/ch0.1.1"
...
...
selftests/unit/test_remote.py
浏览文件 @
ac035ebb
...
...
@@ -38,7 +38,7 @@ class RemoteTestRunnerTest(unittest.TestCase):
remote_no_copy
=
False
,
remote_timeout
=
60
,
show_job_log
=
False
,
multiplex
_files
=
[
'foo.yaml'
,
'bar/baz.yaml'
],
multiplex
=
[
'foo.yaml'
,
'bar/baz.yaml'
],
dry_run
=
True
,
env_keep
=
None
)
log
=
flexmock
()
...
...
@@ -105,7 +105,7 @@ _=/usr/bin/env''', exit_status=0)
args
=
(
"cd ~/avocado/tests; avocado run --force-job-id 1-sleeptest;0 "
"--json - --archive /tests/sleeptest /tests/other/test "
"passtest -
-multiplex
~/avocado/tests/foo.yaml "
"passtest -
m
~/avocado/tests/foo.yaml "
"~/avocado/tests/bar/baz.yaml --dry-run"
)
(
Remote
.
should_receive
(
'run'
)
.
with_args
(
args
,
timeout
=
61
,
ignore_status
=
True
)
...
...
@@ -113,7 +113,7 @@ _=/usr/bin/env''', exit_status=0)
Results
=
flexmock
(
remote
=
Remote
,
urls
=
[
'sleeptest'
],
stream
=
stream
,
timeout
=
None
,
args
=
flexmock
(
show_job_log
=
False
,
multiplex
_files
=
[
'foo.yaml'
,
'bar/baz.yaml'
],
multiplex
=
[
'foo.yaml'
,
'bar/baz.yaml'
],
dry_run
=
True
))
Results
.
should_receive
(
'start_tests'
).
once
().
ordered
()
args
=
{
'status'
:
u
'PASS'
,
'whiteboard'
:
''
,
'time_start'
:
0
,
...
...
selftests/unit/test_tree.py
浏览文件 @
ac035ebb
...
...
@@ -7,6 +7,7 @@ else:
import
unittest
from
avocado.core
import
tree
from
avocado.plugins
import
yaml_to_mux
if
__name__
==
"__main__"
:
PATH_PREFIX
=
"../../../../"
...
...
@@ -16,8 +17,8 @@ else:
class
TestTree
(
unittest
.
TestCase
):
# Share tree with all tests
tree
=
tree
.
create_from_yaml
([
'/:'
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
])
tree
=
yaml_to_mux
.
create_from_yaml
([
'/:'
+
PATH_PREFIX
+
'examples/mux-selftest.yaml'
])
def
test_node_order
(
self
):
self
.
assertIsInstance
(
self
.
tree
,
tree
.
TreeNode
)
...
...
@@ -160,8 +161,9 @@ class TestTree(unittest.TestCase):
tree2
.
children
[
0
].
children
[
2
].
children
[
1
].
value
)
def
test_advanced_yaml
(
self
):
tree2
=
tree
.
create_from_yaml
([
'/:'
+
PATH_PREFIX
+
'examples/mux-'
'selftest-advanced.yaml'
])
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
()
...
...
setup.py
浏览文件 @
ac035ebb
...
...
@@ -138,6 +138,7 @@ if __name__ == '__main__':
'tap = avocado.plugins.tap:TAP'
,
'vm = avocado.plugins.vm:VM'
,
'docker = avocado.plugins.docker:Docker'
,
'yaml_to_mux = avocado.plugins.yaml_to_mux:YamlToMux'
,
],
'avocado.plugins.cli.cmd'
:
[
'config = avocado.plugins.config:Config'
,
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录