Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
avocado
提交
0329006e
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,发现更多精彩内容 >>
未验证
提交
0329006e
编写于
8月 15, 2017
作者:
C
Cleber Rosa
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'apahim/fix_sigint_v6'
Signed-off-by:
N
Cleber Rosa
<
crosa@redhat.com
>
上级
d3d3f1db
204ad707
变更
4
隐藏空白更改
内联
并排
Showing
4 changed file
with
222 addition
and
98 deletion
+222
-98
avocado/core/app.py
avocado/core/app.py
+1
-1
avocado/core/runner.py
avocado/core/runner.py
+2
-1
docs/source/ReferenceGuide.rst
docs/source/ReferenceGuide.rst
+30
-0
selftests/functional/test_interrupt.py
selftests/functional/test_interrupt.py
+189
-96
未找到文件。
avocado/core/app.py
浏览文件 @
0329006e
...
...
@@ -42,7 +42,7 @@ class AvocadoApp(object):
def
sigterm_handler
(
signum
,
frame
):
# pylint: disable=W0613
children
=
process
.
get_children_pids
(
os
.
getpid
())
for
child
in
children
:
process
.
kill_process_tree
(
int
(
child
),
sig
=
signal
.
SIG
TERM
)
process
.
kill_process_tree
(
int
(
child
),
sig
=
signal
.
SIG
KILL
)
raise
SystemExit
(
'Terminated'
)
signal
.
signal
(
signal
.
SIGTERM
,
sigterm_handler
)
...
...
avocado/core/runner.py
浏览文件 @
0329006e
...
...
@@ -443,13 +443,14 @@ class TestRunner(object):
ignore_window
)
stage_1_msg_displayed
=
True
ignore_time_started
=
time
.
time
()
process
.
kill_process_tree
(
proc
.
pid
,
signal
.
SIGINT
)
if
(
ctrl_c_count
>
1
)
and
(
time_elapsed
>
ignore_window
):
if
not
stage_2_msg_displayed
:
abort_reason
=
"Interrupted by ctrl+c (multiple-times)"
self
.
job
.
log
.
debug
(
"Killing test subprocess %s"
,
proc
.
pid
)
stage_2_msg_displayed
=
True
os
.
kill
(
proc
.
pid
,
signal
.
SIGKILL
)
process
.
kill_process_tree
(
proc
.
pid
,
signal
.
SIGKILL
)
# Get/update the test status (decrease timeout on abort)
if
abort_reason
:
...
...
docs/source/ReferenceGuide.rst
浏览文件 @
0329006e
...
...
@@ -653,4 +653,34 @@ be a valid one:
* An end of string (or end of line) must immediately follow the
content.
.. _signal_handlers:
Signal Handlers
===============
Avocado normal operation is related to run code written by
users/test-writers. It means the test code can carry its own handlers
for different signals or even ignore then. Still, as the code is being
executed by Avocado, we have to make sure we will finish all the
subprocesses we create before ending our execution.
Signals sent to the Avocado main process will be handled as follows:
- SIGSTP/Ctrl+Z: On SIGSTP, Avocado will pause the execution of the
subprocesses, while the main process will still be running,
respecting the timeout timer and waiting for the subprocesses to
finish. A new SIGSTP will make the subprocesses to resume the
execution.
- SIGINT/Ctrl+C: This signal will be forwarded to the test process and
Avocado will wait until it's finished. If the test process does not
finish after receiving a SIGINT, user can send a second SIGINT (after
the 2 seconds ignore period). The second SIGINT will make Avocado
to send a SIGKILL to the whole subprocess tree and then complete the
main process execution.
- SIGTERM: This signal will make Avocado to terminate immediately. A
SIGKILL will be sent to the whole subprocess tree and the main process
will exit without completing the execution. Notice that it's a
best-effort attempt, meaning that in case of fork-bomb, newly created
processes might still be left behind.
.. [#f1] Avocado plugins can introduce additional test types.
selftests/functional/test_interrupt.py
浏览文件 @
0329006e
import
os
import
tempfile
import
time
import
signal
import
shutil
import
stat
import
subprocess
import
unittest
import
aexpect
import
psutil
from
avocado.utils
import
process
from
avocado.utils
import
wait
from
avocado.utils
import
script
from
avocado.utils
import
data_factory
...
...
@@ -25,13 +27,20 @@ DEFAULT_MODE = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR |
stat
.
S_IROTH
|
stat
.
S_IXOTH
)
BAD_TEST
=
"""#!/usr/bin/env python
import multiprocessing
import signal
import time
def foo():
while True:
time.sleep(0.1)
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_IGN)
signal.signal(signal.SIGTERM, signal.SIG_IGN)
signal.signal(signal.SIGQUIT, signal.SIG_IGN)
proc = multiprocessing.Process(target=foo)
proc.start()
while True:
time.sleep(0.1)
"""
...
...
@@ -52,13 +61,54 @@ if __name__ == "__main__":
class
InterruptTest
(
unittest
.
TestCase
):
def
_has_children
(
self
):
return
len
(
psutil
.
Process
(
self
.
proc
.
pid
).
children
())
>
0
def
_is_finished
(
self
):
return
self
.
proc
.
poll
()
is
not
None
def
_no_test_in_process_table
(
self
):
"""
Make sure the test will be really gone from the
process table.
"""
test_processes
=
[]
old_psutil
=
False
try
:
process_list
=
psutil
.
pids
()
except
AttributeError
:
process_list
=
psutil
.
get_pid_list
()
old_psutil
=
True
for
p
in
process_list
:
try
:
p_obj
=
psutil
.
Process
(
p
)
if
p_obj
is
not
None
:
if
old_psutil
:
cmdline_list
=
psutil
.
Process
(
p
).
cmdline
else
:
try
:
cmdline_list
=
psutil
.
Process
(
p
).
cmdline
()
except
psutil
.
AccessDenied
:
cmdline_list
=
[]
if
self
.
test_module
in
" "
.
join
(
cmdline_list
):
test_processes
.
append
(
p_obj
)
# psutil.NoSuchProcess happens when the original
# process already ended and left the process table
except
psutil
.
NoSuchProcess
:
pass
return
len
(
test_processes
)
==
0
def
setUp
(
self
):
self
.
tmpdir
=
tempfile
.
mkdtemp
(
prefix
=
'avocado_'
+
__name__
)
self
.
test_module
=
None
@
unittest
.
skipIf
(
int
(
os
.
environ
.
get
(
"AVOCADO_CHECK_LEVEL"
,
0
))
<
2
,
"Skipping test that take a long time to run, are "
"resource intensive or time sensitve"
)
def
test_badly_behaved
(
self
):
def
test_badly_behaved
_sigint
(
self
):
"""
Make sure avocado can cleanly get out of a loop of badly behaved tests.
"""
...
...
@@ -67,64 +117,86 @@ class InterruptTest(unittest.TestCase):
bad_test
=
script
.
TemporaryScript
(
bad_test_basename
,
BAD_TEST
,
'avocado_interrupt_test'
,
mode
=
DEFAULT_MODE
)
bad_test
.
save
()
self
.
test_module
=
bad_test
.
path
os
.
chdir
(
basedir
)
cmd_line
=
(
'%s run --sysinfo=off --job-results-dir %s '
'%s %s %s'
%
(
AVOCADO
,
self
.
tmpdir
,
bad_test
.
path
,
bad_test
.
path
,
bad_test
.
path
))
proc
=
aexpect
.
Expect
(
command
=
cmd_line
,
linesep
=
''
)
proc
.
read_until_last_line_matches
(
os
.
path
.
basename
(
bad_test
.
path
))
proc
.
sendline
(
'
\x03
'
)
proc
.
read_until_last_line_matches
(
'Interrupt requested. Waiting 2 '
'seconds for test to finish '
'(ignoring new Ctrl+C until then)'
)
# We have to actually wait 2 seconds until the ignore window is over
cmd
=
(
'%s run %s --sysinfo=off --job-results-dir %s '
%
(
AVOCADO
,
self
.
test_module
,
self
.
tmpdir
))
self
.
proc
=
subprocess
.
Popen
(
cmd
.
split
(),
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
)
if
not
wait
.
wait_for
(
self
.
_has_children
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado did not start the test process.'
)
# This test will ignore SIGINT, so it should terminate
# when we send the second SIGINT.
os
.
kill
(
self
.
proc
.
pid
,
signal
.
SIGINT
)
# We have to actually wait 2+ seconds until
# the ignore window is over
time
.
sleep
(
2.5
)
proc
.
sendline
(
'
\x03
'
)
proc
.
read_until_last_line_matches
(
'JOB TIME : %d s'
)
wait
.
wait_for
(
lambda
:
not
proc
.
is_alive
(),
timeout
=
1
)
os
.
kill
(
self
.
proc
.
pid
,
signal
.
SIGINT
)
# Make sure the bad test will be really gone from the process table
def
wait_until_no_badtest
():
bad_test_processes
=
[]
if
not
wait
.
wait_for
(
self
.
_is_finished
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado was still running after receiving SIGINT '
'twice.'
)
old_psutil
=
False
try
:
process_list
=
psutil
.
pids
()
except
AttributeError
:
process_list
=
psutil
.
get_pid_list
()
old_psutil
=
True
for
p
in
process_list
:
try
:
p_obj
=
psutil
.
Process
(
p
)
if
p_obj
is
not
None
:
if
old_psutil
:
cmdline_list
=
psutil
.
Process
(
p
).
cmdline
else
:
try
:
cmdline_list
=
psutil
.
Process
(
p
).
cmdline
()
except
psutil
.
AccessDenied
:
cmdline_list
=
[]
if
bad_test
.
path
in
" "
.
join
(
cmdline_list
):
bad_test_processes
.
append
(
p_obj
)
# psutil.NoSuchProcess happens when the original
# process already ended and left the process table
except
psutil
.
NoSuchProcess
:
pass
return
len
(
bad_test_processes
)
==
0
wait
.
wait_for
(
wait_until_no_badtest
,
timeout
=
2
)
self
.
assertTrue
(
wait
.
wait_for
(
self
.
_no_test_in_process_table
,
timeout
=
2
),
'Avocado left processes behind.'
)
output
=
self
.
proc
.
stdout
.
read
()
# Make sure the Interrupted requested sentence is there
self
.
assertIn
(
'Interrupt requested. Waiting 2 seconds for test to '
'finish (ignoring new Ctrl+C until then)'
,
output
)
# Make sure the Killing test subprocess message did appear
self
.
assertIn
(
'Killing test subprocess'
,
proc
.
get_output
())
self
.
assertIn
(
'Killing test subprocess'
,
output
)
@
unittest
.
skipIf
(
int
(
os
.
environ
.
get
(
"AVOCADO_CHECK_LEVEL"
,
0
))
<
2
,
"Skipping test that take a long time to run, are "
"resource intensive or time sensitve"
)
def
test_badly_behaved_sigterm
(
self
):
"""
Make sure avocado can cleanly get out of a loop of badly behaved tests.
"""
bad_test_basename
=
(
'wontquit-%s'
%
data_factory
.
generate_random_string
(
5
))
bad_test
=
script
.
TemporaryScript
(
bad_test_basename
,
BAD_TEST
,
'avocado_interrupt_test'
,
mode
=
DEFAULT_MODE
)
bad_test
.
save
()
self
.
test_module
=
bad_test
.
path
os
.
chdir
(
basedir
)
cmd
=
(
'%s run %s --sysinfo=off --job-results-dir %s '
%
(
AVOCADO
,
self
.
test_module
,
self
.
tmpdir
))
self
.
proc
=
subprocess
.
Popen
(
cmd
.
split
(),
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
)
if
not
wait
.
wait_for
(
self
.
_has_children
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado did not start the test process.'
)
# This test should be terminated when the main process
# receives a SIGTERM, even if the test process ignores SIGTERM.
os
.
kill
(
self
.
proc
.
pid
,
signal
.
SIGTERM
)
if
not
wait
.
wait_for
(
self
.
_is_finished
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado was still running after receiving SIGINT '
'twice.'
)
self
.
assertTrue
(
wait
.
wait_for
(
self
.
_no_test_in_process_table
,
timeout
=
2
),
'Avocado left processes behind.'
)
# Make sure the Interrupted test sentence is there
self
.
assertIn
(
'Terminated
\n
'
,
self
.
proc
.
stdout
.
read
())
@
unittest
.
skipIf
(
int
(
os
.
environ
.
get
(
"AVOCADO_CHECK_LEVEL"
,
0
))
<
1
,
"Skipping test that take a long time to run, are "
"resource intensive or time sensitve"
)
def
test_well_behaved
(
self
):
def
test_well_behaved
_sigint
(
self
):
"""
Make sure avocado can cleanly get out of a loop of well behaved tests.
"""
...
...
@@ -134,55 +206,76 @@ class InterruptTest(unittest.TestCase):
'avocado_interrupt_test'
,
mode
=
DEFAULT_MODE
)
good_test
.
save
()
self
.
test_module
=
good_test
.
path
os
.
chdir
(
basedir
)
cmd_line
=
(
'%s run --sysinfo=off --job-results-dir %s '
'%s %s %s'
%
(
AVOCADO
,
self
.
tmpdir
,
good_test
.
path
,
good_test
.
path
,
good_test
.
path
))
proc
=
aexpect
.
Expect
(
command
=
cmd_line
,
linesep
=
''
)
proc
.
read_until_last_line_matches
(
os
.
path
.
basename
(
good_test
.
path
))
proc
.
sendline
(
'
\x03
'
)
proc
.
read_until_last_line_matches
(
'JOB TIME : %d s'
)
wait
.
wait_for
(
lambda
:
not
proc
.
is_alive
(),
timeout
=
1
)
# Make sure the good test will be really gone from the process table
def
wait_until_no_goodtest
():
good_test_processes
=
[]
old_psutil
=
False
try
:
process_list
=
psutil
.
pids
()
except
AttributeError
:
process_list
=
psutil
.
get_pid_list
()
old_psutil
=
True
for
p
in
process_list
:
try
:
p_obj
=
psutil
.
Process
(
p
)
if
p_obj
is
not
None
:
if
old_psutil
:
cmdline_list
=
psutil
.
Process
(
p
).
cmdline
else
:
try
:
cmdline_list
=
psutil
.
Process
(
p
).
cmdline
()
except
psutil
.
AccessDenied
:
cmdline_list
=
[]
if
good_test
.
path
in
" "
.
join
(
cmdline_list
):
good_test_processes
.
append
(
p_obj
)
# psutil.NoSuchProcess happens when the original
# process already ended and left the process table
except
psutil
.
NoSuchProcess
:
pass
return
len
(
good_test_processes
)
==
0
wait
.
wait_for
(
wait_until_no_goodtest
,
timeout
=
2
)
# Make sure the Killing test subprocess message is not there
self
.
assertNotIn
(
'Killing test subprocess'
,
proc
.
get_output
())
cmd
=
(
'%s run %s --sysinfo=off --job-results-dir %s '
%
(
AVOCADO
,
self
.
test_module
,
self
.
tmpdir
))
self
.
proc
=
subprocess
.
Popen
(
cmd
.
split
(),
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
)
if
not
wait
.
wait_for
(
self
.
_has_children
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado did not start the test process.'
)
# This test will not ignore SIGINT, so it should
# terminate right away.
os
.
kill
(
self
.
proc
.
pid
,
signal
.
SIGINT
)
if
not
wait
.
wait_for
(
self
.
_is_finished
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado was still running after receiving SIGINT '
'twice.'
)
self
.
assertTrue
(
wait
.
wait_for
(
self
.
_no_test_in_process_table
,
timeout
=
2
),
'Avocado left processes behind.'
)
output
=
self
.
proc
.
stdout
.
read
()
# Make sure the Interrupted requested sentence is there
self
.
assertIn
(
'Interrupt requested. Waiting 2 seconds for test to '
'finish (ignoring new Ctrl+C until then)'
,
proc
.
get_output
())
'finish (ignoring new Ctrl+C until then)'
,
output
)
# Make sure the Killing test subprocess message is not there
self
.
assertNotIn
(
'Killing test subprocess'
,
output
)
@
unittest
.
skipIf
(
int
(
os
.
environ
.
get
(
"AVOCADO_CHECK_LEVEL"
,
0
))
<
1
,
"Skipping test that take a long time to run, are "
"resource intensive or time sensitve"
)
def
test_well_behaved_sigterm
(
self
):
"""
Make sure avocado can cleanly get out of a loop of well behaved tests.
"""
good_test_basename
=
(
'goodtest-%s.py'
%
data_factory
.
generate_random_string
(
5
))
good_test
=
script
.
TemporaryScript
(
good_test_basename
,
GOOD_TEST
,
'avocado_interrupt_test'
,
mode
=
DEFAULT_MODE
)
good_test
.
save
()
self
.
test_module
=
good_test
.
path
os
.
chdir
(
basedir
)
cmd
=
(
'%s run %s --sysinfo=off --job-results-dir %s '
%
(
AVOCADO
,
self
.
test_module
,
self
.
tmpdir
))
self
.
proc
=
subprocess
.
Popen
(
cmd
.
split
(),
stdout
=
subprocess
.
PIPE
,
stderr
=
subprocess
.
STDOUT
)
if
not
wait
.
wait_for
(
self
.
_has_children
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado did not start the test process.'
)
# This test should be terminated when the main process
# receives a SIGTERM.
os
.
kill
(
self
.
proc
.
pid
,
signal
.
SIGTERM
)
if
not
wait
.
wait_for
(
self
.
_is_finished
,
timeout
=
2
):
process
.
kill_process_tree
(
self
.
proc
.
pid
)
self
.
fail
(
'Avocado was still running after receiving SIGINT '
'twice.'
)
self
.
assertTrue
(
wait
.
wait_for
(
self
.
_no_test_in_process_table
,
timeout
=
2
),
'Avocado left processes behind.'
)
# Make sure the Interrupted test sentence is there
self
.
assertIn
(
'Terminated
\n
'
,
self
.
proc
.
stdout
.
read
())
def
tearDown
(
self
):
shutil
.
rmtree
(
self
.
tmpdir
)
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录