Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
openeuler
avocado
提交
e9481134
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,发现更多精彩内容 >>
提交
e9481134
编写于
6月 08, 2016
作者:
A
Amador Pahim
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #1246 from ldoktor/warn-on-queue-error
avocado: Improve TestStatus handling
上级
077d0fc0
d665269b
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
157 addition
and
60 deletion
+157
-60
avocado/core/runner.py
avocado/core/runner.py
+87
-49
selftests/functional/test_basic.py
selftests/functional/test_basic.py
+67
-9
selftests/functional/test_job_timeout.py
selftests/functional/test_job_timeout.py
+3
-2
未找到文件。
avocado/core/runner.py
浏览文件 @
e9481134
...
...
@@ -32,7 +32,6 @@ from . import status
from
.loader
import
loader
from
.status
import
mapping
from
..utils
import
wait
from
..utils
import
stacktrace
from
..utils
import
runtime
from
..utils
import
process
...
...
@@ -40,6 +39,40 @@ TEST_LOG = logging.getLogger("avocado.test")
APP_LOG
=
logging
.
getLogger
(
"avocado.app"
)
def
add_runner_failure
(
test_state
,
new_status
,
message
):
"""
Append runner failure to the overall test status.
:param test_state: Original test state (dict)
:param new_status: New test status (PASS/FAIL/ERROR/INTERRUPTED/...)
:param message: The error message
"""
# Try to propagate the message everywhere
message
=
(
"Runner error occurred: %s
\n
Original status: %s
\n
%s"
%
(
message
,
test_state
.
get
(
"status"
),
test_state
))
TEST_LOG
.
error
(
message
)
test_log
=
test_state
.
get
(
"logfile"
)
if
test_state
.
get
(
"text_output"
):
test_state
[
"text_output"
]
=
"%s
\n
%s
\n
"
%
(
test_state
[
"text_output"
],
message
)
else
:
test_state
[
"text_output"
]
=
message
+
"
\n
"
if
test_log
:
open
(
test_log
,
"a"
).
write
(
'
\n
'
+
message
+
'
\n
'
)
# Update the results
if
test_state
.
get
(
"fail_reason"
):
test_state
[
"fail_reason"
]
=
"%s
\n
%s"
%
(
test_state
[
"fail_reason"
],
message
)
else
:
test_state
[
"fail_reason"
]
=
message
if
test_state
.
get
(
"fail_class"
):
test_state
[
"fail_class"
]
=
"%s
\n
RUNNER"
%
test_state
[
"fail_class"
]
else
:
test_state
[
"fail_class"
]
=
"RUNNER"
test_state
[
"status"
]
=
new_status
return
test_state
class
TestStatus
(
object
):
"""
...
...
@@ -56,6 +89,7 @@ class TestStatus(object):
self
.
_early_status
=
None
self
.
status
=
{}
self
.
interrupt
=
None
self
.
_failed
=
False
def
_get_msg_from_queue
(
self
):
"""
...
...
@@ -69,10 +103,8 @@ class TestStatus(object):
# Let's catch all exceptions, since errors here mean a
# crash in avocado.
except
Exception
as
details
:
APP_LOG
.
error
(
"
\n
Error receiving message from test: %s -> %s"
,
details
.
__class__
,
details
)
stacktrace
.
log_exc_info
(
sys
.
exc_info
(),
'avocado.app.tracebacks'
)
self
.
_failed
=
True
TEST_LOG
.
error
(
"RUNNER: Failed to read queue: %s"
,
details
)
return
None
@
property
...
...
@@ -149,33 +181,57 @@ class TestStatus(object):
else
:
# test_status
self
.
status
=
msg
def
abort
(
self
,
proc
,
started
,
timeout
,
first
,
step
):
def
_add_status_failures
(
self
,
test_state
):
"""
Append TestStatus error to test_state in case there were any.
"""
Handle job abortion
if
self
.
_failed
:
return
add_runner_failure
(
test_state
,
"ERROR"
,
"TestStatus failed,"
" see overall job.log for details."
)
return
test_state
def
finish
(
self
,
proc
,
started
,
timeout
,
step
):
"""
Wait for the test process to finish and report status or error status
if unable to obtain the status till deadline.
:param proc: The test's process
:param started: Time when the test started
:param timeout: Timeout for waiting on status
:param first: Delay before first check
:param step: Step between checks for the status
"""
if
proc
.
is_alive
()
and
wait
.
wait_for
(
lambda
:
self
.
status
,
timeout
,
first
,
step
):
status
=
self
.
status
else
:
test_state
=
self
.
early_status
test_state
[
'time_elapsed'
]
=
time
.
time
()
-
started
test_state
[
'fail_reason'
]
=
'Test process aborted'
test_state
[
'status'
]
=
exceptions
.
TestAbortError
.
status
test_state
[
'fail_class'
]
=
(
exceptions
.
TestAbortError
.
__class__
.
__name__
)
test_state
[
'traceback'
]
=
'Traceback not available'
with
open
(
test_state
[
'logfile'
],
'r'
)
as
log_file_obj
:
test_state
[
'text_output'
]
=
log_file_obj
.
read
()
TEST_LOG
.
error
(
'ERROR %s -> TestAbortedError: '
'Test aborted unexpectedly'
,
test_state
[
'name'
])
status
=
test_state
# Wait for either process termination or test status
wait
.
wait_for
(
lambda
:
not
proc
.
is_alive
()
or
self
.
status
,
timeout
,
0
,
step
)
if
self
.
status
:
# status exists, wait for process to finish
if
not
wait
.
wait_for
(
lambda
:
not
proc
.
is_alive
(),
timeout
,
0
,
step
):
err
=
"Test reported status but did not finish"
else
:
# Test finished and reported status, pass
return
self
.
_add_status_failures
(
self
.
status
)
else
:
# proc finished, wait for late status delivery
if
not
wait
.
wait_for
(
lambda
:
self
.
status
,
timeout
,
0
,
step
):
err
=
"Test died without reporting the status."
else
:
# Status delivered after the test process finished, pass
return
self
.
_add_status_failures
(
self
.
status
)
# At this point there were failures, fill the new test status
TEST_LOG
.
debug
(
"Original status: %s"
,
str
(
self
.
status
))
test_state
=
self
.
early_status
test_state
[
'time_elapsed'
]
=
time
.
time
()
-
started
test_state
[
'fail_reason'
]
=
err
test_state
[
'status'
]
=
exceptions
.
TestAbortError
.
status
test_state
[
'fail_class'
]
=
(
exceptions
.
TestAbortError
.
__class__
.
__name__
)
test_state
[
'traceback'
]
=
'Traceback not available'
with
open
(
test_state
[
'logfile'
],
'r'
)
as
log_file_obj
:
test_state
[
'text_output'
]
=
log_file_obj
.
read
()
TEST_LOG
.
error
(
'ERROR %s -> TestAbortedError: '
'Test process died without reporting the status.'
,
test_state
[
'name'
])
if
proc
.
is_alive
():
TEST_LOG
.
warning
(
"Killing hanged test process %s"
%
proc
.
pid
)
for
_
in
xrange
(
5
):
# I really want to destroy it
os
.
kill
(
proc
.
pid
,
signal
.
SIGKILL
)
if
not
proc
.
is_alive
():
...
...
@@ -184,7 +240,7 @@ class TestStatus(object):
else
:
raise
exceptions
.
TestError
(
"Unable to destroy test's process "
"(%s)"
%
proc
.
pid
)
return
s
tatus
return
s
elf
.
_add_status_failures
(
test_state
)
class
TestRunner
(
object
):
...
...
@@ -370,25 +426,13 @@ class TestRunner(object):
os
.
kill
(
proc
.
pid
,
signal
.
SIGKILL
)
# Get/update the test status
if
test_status
.
status
:
test_state
=
test_status
.
status
else
:
test_state
=
test_status
.
abort
(
proc
,
time_started
,
cycle_timeout
,
first
,
step
)
test_state
=
test_status
.
finish
(
proc
,
time_started
,
cycle_timeout
,
step
)
# Try to log the timeout reason to test's results and update test_state
if
abort_reason
:
TEST_LOG
.
error
(
abort_reason
)
test_log
=
test_state
.
get
(
"logfile"
)
if
test_log
:
open
(
test_log
,
"a"
).
write
(
"
\n
RUNNER: "
+
abort_reason
+
"
\n
"
)
if
test_state
.
get
(
"text_output"
):
test_state
[
"text_output"
]
+=
"
\n
RUNNER: "
+
abort_reason
+
"
\n
"
else
:
test_state
[
"text_output"
]
=
abort_reason
test_state
[
"status"
]
=
"INTERRUPTED"
test_state
[
"fail_reason"
]
=
abort_reason
test_state
[
"fail_class"
]
=
"RUNNER"
test_state
=
add_runner_failure
(
test_state
,
"INTERRUPTED"
,
abort_reason
)
# don't process other tests from the list
if
ctrl_c_count
>
0
:
...
...
@@ -396,14 +440,8 @@ class TestRunner(object):
# Make sure the test status is correct
if
test_state
.
get
(
'status'
)
not
in
status
.
user_facing_status
:
test_state
[
'fail_reason'
]
=
(
"Test reports unsupported test "
"status %s.
\n
Original fail_reason: %s"
"
\n
Original fail_class: %s"
%
(
test_state
.
get
(
'status'
),
test_state
.
get
(
'fail_reason'
),
test_state
.
get
(
'fail_class'
)))
test_state
[
'fail_class'
]
=
"RUNNER"
test_state
[
'status'
]
=
'ERROR'
test_state
=
add_runner_failure
(
test_state
,
"ERROR"
,
"Test reports"
" unsupported test status."
)
self
.
result
.
check_test
(
test_state
)
if
test_state
[
'status'
]
==
"INTERRUPTED"
:
...
...
selftests/functional/test_basic.py
浏览文件 @
e9481134
...
...
@@ -75,6 +75,27 @@ class MyTest(Test):
'''
REPORTS_STATUS_AND_HANG
=
'''
from avocado import Test
import time
class MyTest(Test):
def test(self):
self.runner_queue.put({"running": False})
time.sleep(60)
'''
DIE_WITHOUT_REPORTING_STATUS
=
'''
from avocado import Test
import os
import signal
class MyTest(Test):
def test(self):
os.kill(os.getpid(), signal.SIGKILL)
'''
class
RunnerOperationTest
(
unittest
.
TestCase
):
def
setUp
(
self
):
...
...
@@ -180,7 +201,43 @@ class RunnerOperationTest(unittest.TestCase):
self
.
assertEqual
(
results
[
"tests"
][
0
][
"status"
],
"ERROR"
,
"%s != %s
\n
%s"
%
(
results
[
"tests"
][
0
][
"status"
],
"ERROR"
,
res
))
self
.
assertIn
(
"Original fail_reason: None"
,
self
.
assertIn
(
"Runner error occurred: Test reports unsupported"
,
results
[
"tests"
][
0
][
"fail_reason"
])
def
test_hanged_test_with_status
(
self
):
""" Check that avocado handles hanged tests properly """
os
.
chdir
(
basedir
)
with
script
.
TemporaryScript
(
"report_status_and_hang.py"
,
REPORTS_STATUS_AND_HANG
,
"hanged_test_with_status"
)
as
tst
:
res
=
process
.
run
(
"./scripts/avocado run --sysinfo=off "
"--job-results-dir %s %s --json -"
%
(
self
.
tmpdir
,
tst
),
ignore_status
=
True
)
self
.
assertEqual
(
res
.
exit_status
,
exit_codes
.
AVOCADO_TESTS_FAIL
)
results
=
json
.
loads
(
res
.
stdout
)
self
.
assertEqual
(
results
[
"tests"
][
0
][
"status"
],
"ERROR"
,
"%s != %s
\n
%s"
%
(
results
[
"tests"
][
0
][
"status"
],
"ERROR"
,
res
))
self
.
assertIn
(
"Test reported status but did not finish"
,
results
[
"tests"
][
0
][
"fail_reason"
])
self
.
assertLess
(
res
.
duration
,
40
,
"Test execution took too long, "
"which is likely because the hanged test was not "
"interrupted. Results:
\n
%s"
%
res
)
def
test_no_status_reported
(
self
):
os
.
chdir
(
basedir
)
with
script
.
TemporaryScript
(
"die_without_reporting_status.py"
,
DIE_WITHOUT_REPORTING_STATUS
,
"no_status_reported"
)
as
tst
:
res
=
process
.
run
(
"./scripts/avocado run --sysinfo=off "
"--job-results-dir %s %s --json -"
%
(
self
.
tmpdir
,
tst
),
ignore_status
=
True
)
self
.
assertEqual
(
res
.
exit_status
,
exit_codes
.
AVOCADO_TESTS_FAIL
)
results
=
json
.
loads
(
res
.
stdout
)
self
.
assertEqual
(
results
[
"tests"
][
0
][
"status"
],
"ERROR"
,
"%s != %s
\n
%s"
%
(
results
[
"tests"
][
0
][
"status"
],
"ERROR"
,
res
))
self
.
assertIn
(
"Test died without reporting the status"
,
results
[
"tests"
][
0
][
"fail_reason"
])
def
test_runner_tests_fail
(
self
):
...
...
@@ -256,7 +313,7 @@ class RunnerOperationTest(unittest.TestCase):
"Avocado crashed (rc %d):
\n
%s"
%
(
unexpected_rc
,
result
))
self
.
assertEqual
(
result
.
exit_status
,
expected_rc
,
"Avocado did not return rc %d:
\n
%s"
%
(
expected_rc
,
result
))
self
.
assertIn
(
"R
UNNER
: Timeout reached"
,
output
,
self
.
assertIn
(
"R
unner error occurred
: Timeout reached"
,
output
,
"Timeout reached message not found in the output:
\n
%s"
%
output
)
# Ensure no test aborted error messages show up
self
.
assertNotIn
(
"TestAbortedError: Test aborted unexpectedly"
,
output
)
...
...
@@ -267,7 +324,7 @@ class RunnerOperationTest(unittest.TestCase):
'--xunit - abort.py'
%
self
.
tmpdir
)
result
=
process
.
run
(
cmd_line
,
ignore_status
=
True
)
output
=
result
.
stdout
excerpt
=
'Test
process aborted
'
excerpt
=
'Test
died without reporting the status.
'
expected_rc
=
exit_codes
.
AVOCADO_TESTS_FAIL
unexpected_rc
=
exit_codes
.
AVOCADO_FAIL
self
.
assertNotEqual
(
result
.
exit_status
,
unexpected_rc
,
...
...
@@ -637,12 +694,13 @@ class RunnerSimpleTest(unittest.TestCase):
debug_log
=
os
.
path
.
join
(
self
.
tmpdir
,
"latest"
,
"test-results"
,
sleep_dir
,
"debug.log"
)
debug_log
=
open
(
debug_log
).
read
()
self
.
assertIn
(
"RUNNER: Timeout reached"
,
debug_log
,
"RUNNER: Timeout "
"reached message not in the test's debug.log:
\n
%s"
%
debug_log
)
self
.
assertNotIn
(
"Traceback"
,
debug_log
,
"Traceback present in the "
"test's debug.log file, but it was suppose to be "
"stopped and unable to produce it.
\n
%s"
%
debug_log
)
self
.
assertIn
(
"Runner error occurred: Timeout reached"
,
debug_log
,
"Runner error occurred: Timeout reached message not "
"in the test's debug.log:
\n
%s"
%
debug_log
)
self
.
assertNotIn
(
"Traceback (most recent"
,
debug_log
,
"Traceback "
"present in the test's debug.log file, but it was "
"suppose to be stopped and unable to produce it.
\n
"
"%s"
%
debug_log
)
def
tearDown
(
self
):
self
.
pass_script
.
remove
()
...
...
selftests/functional/test_job_timeout.py
浏览文件 @
e9481134
...
...
@@ -103,8 +103,9 @@ class JobTimeOutTest(unittest.TestCase):
res_dir
=
os
.
path
.
join
(
self
.
tmpdir
,
"latest"
,
"test-results"
)
debug_log
=
glob
.
glob
(
os
.
path
.
join
(
res_dir
,
"%s-*"
%
idx
,
"debug.log"
))
debug_log
=
open
(
debug_log
[
0
]).
read
()
self
.
assertIn
(
"RUNNER: Timeout reached"
,
debug_log
,
"RUNNER: Timeout "
"reached message not in the %sst test's debug.log:
\n
%s"
self
.
assertIn
(
"Runner error occurred: Timeout reached"
,
debug_log
,
"Runner error occurred: Timeout reached message not "
"in the %sst test's debug.log:
\n
%s"
%
(
idx
,
debug_log
))
self
.
assertIn
(
"Traceback (most recent call last)"
,
debug_log
,
"Traceback not present in the %sst test's debug.log:
\n
%s"
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录