tester: Correctly handle contro-c.

Add support to kill running tests if the user presses control-c.
This commit is contained in:
Chris Johns
2014-05-31 20:03:05 +10:00
parent 5cdcde1ec2
commit c04a84917a
5 changed files with 164 additions and 18 deletions

View File

@@ -116,6 +116,7 @@ class execute(object):
self.environment = None self.environment = None
self.outputting = False self.outputting = False
self.timing_out = False self.timing_out = False
self.proc = None
def _capture(self, command, proc, timeout = None): def _capture(self, command, proc, timeout = None):
"""Create 3 threads to read stdout and stderr and send to the output handler """Create 3 threads to read stdout and stderr and send to the output handler
@@ -262,12 +263,25 @@ class execute(object):
timeout_thread.daemon = True timeout_thread.daemon = True
timeout_thread.start() timeout_thread.start()
try: try:
self.lock.acquire()
try:
self.proc = proc
except:
raise
finally:
self.lock.release()
exitcode = proc.wait() exitcode = proc.wait()
except: except:
print 'killing'
proc.kill() proc.kill()
raise raise
finally: finally:
self.lock.acquire()
try:
self.proc = None
except:
raise
finally:
self.lock.release()
if self.cleanup: if self.cleanup:
self.cleanup(proc) self.cleanup(proc)
if timeout_thread: if timeout_thread:
@@ -415,6 +429,37 @@ class execute(object):
self.environment = environment self.environment = environment
return old_environment return old_environment
def kill(self):
self.lock.acquire()
try:
if self.proc is not None:
self.proc.kill()
except:
raise
finally:
self.lock.release()
def terminate(self):
self.lock.acquire()
try:
if self.proc is not None:
self.proc.terminate()
except:
raise
finally:
self.lock.release()
def send_signal(self, signal):
self.lock.acquire()
try:
if self.proc is not None:
print "sending sig"
self.proc.send_signal(signal)
except:
raise
finally:
self.lock.release()
class capture_execution(execute): class capture_execution(execute):
"""Capture all output as a string and return it.""" """Capture all output as a string and return it."""

View File

@@ -0,0 +1,43 @@
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2013-2014 Chris Johns (chrisj@rtems.org)
# All rights reserved.
#
# This file is part of the RTEMS Tools package in 'rtems-tools'.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
import sys
import traceback
def trace():
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# thread-id: %s" % threadId)
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('file: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
return '\n'.join(code)

41
tester/rt/bsps.py Normal file
View File

@@ -0,0 +1,41 @@
#
# RTEMS Tools Project (http://www.rtems.org/)
# Copyright 2013-2014 Chris Johns (chrisj@rtems.org)
# All rights reserved.
#
# This file is part of the RTEMS Tools package in 'rtems-tools'.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
from rtemstoolkit import error
from rtemstoolkit import log
from rtemstoolkit import path
def list(opts):
path_ = opts.defaults.expand('%%{_configdir}/bsps/*.mc')
bsps = path.collect_files(path_)
log.notice(' BSP List:')
for bsp in bsps:
log.notice(' %s' % (path.basename(bsp[:-3])))
raise error.exit()

View File

@@ -62,7 +62,7 @@ class file(config.file):
self.console = None self.console = None
self.output = None self.output = None
self.report = report self.report = report
self.load(name) self.name = name
def __del__(self): def __del__(self):
if self.console: if self.console:
@@ -181,6 +181,9 @@ class file(config.file):
for l in text: for l in text:
print ' '.join(l) print ' '.join(l)
def run(self):
self.load(self.name)
def capture(self, text): def capture(self, text):
text = [(']', l) for l in text.replace(chr(13), '').splitlines()] text = [(']', l) for l in text.replace(chr(13), '').splitlines()]
self._lock() self._lock()
@@ -203,3 +206,7 @@ class file(config.file):
if flag in dt.split(','): if flag in dt.split(','):
return True return True
return False return False
def kill(self):
if self.process:
self.process.kill()

View File

@@ -38,7 +38,9 @@ import time
from rtemstoolkit import error from rtemstoolkit import error
from rtemstoolkit import log from rtemstoolkit import log
from rtemstoolkit import path from rtemstoolkit import path
from rtemstoolkit import stacktraces
import bsps
import config import config
import console import console
import options import options
@@ -46,17 +48,6 @@ import report
import version import version
import fnmatch import fnmatch
def stacktraces():
import traceback
code = []
for threadId, stack in sys._current_frames().items():
code.append("\n# thread-id: %s" % threadId)
for filename, lineno, name, line in traceback.extract_stack(stack):
code.append('file: "%s", line %d, in %s' % (filename, lineno, name))
if line:
code.append(" %s" % (line.strip()))
return '\n'.join(code)
class test(object): class test(object):
def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts): def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
self.index = index self.index = index
@@ -78,7 +69,15 @@ class test(object):
if not path.isdir(rtems_tools_bin): if not path.isdir(rtems_tools_bin):
raise error.general('cannot find RTEMS tools path: %s' % (rtems_tools_bin)) raise error.general('cannot find RTEMS tools path: %s' % (rtems_tools_bin))
self.opts.defaults['rtems_tools'] = rtems_tools_bin self.opts.defaults['rtems_tools'] = rtems_tools_bin
self.config = config.file(report, bsp_config, self.opts) self.config = config.file(self.report, self.bsp_config, self.opts)
def run(self):
if self.config:
self.config.run()
def kill(self):
if self.config:
self.config.kill()
class test_run(object): class test_run(object):
def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts): def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
@@ -102,6 +101,7 @@ class test_run(object):
self.executable, self.rtems_tools, self.executable, self.rtems_tools,
self.bsp, self.bsp_config, self.bsp, self.bsp_config,
self.opts) self.opts)
self.test.run()
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
except: except:
@@ -120,6 +120,10 @@ class test_run(object):
if self.result is not None: if self.result is not None:
raise self.result[0], self.result[1], self.result[2] raise self.result[0], self.result[1], self.result[2]
def kill(self):
if self.test:
self.test.kill()
def find_executables(paths, glob): def find_executables(paths, glob):
executables = [] executables = []
for p in paths: for p in paths:
@@ -175,8 +179,13 @@ def list_bsps(opts):
log.notice(' %s' % (path.basename(bsp[:-3]))) log.notice(' %s' % (path.basename(bsp[:-3])))
raise error.exit() raise error.exit()
def killall(tests):
for test in tests:
test.kill()
def run(command_path = None): def run(command_path = None):
import sys import sys
tests = []
stdtty = console.save() stdtty = console.save()
opts = None opts = None
default_exefilter = '*.exe' default_exefilter = '*.exe'
@@ -194,7 +203,7 @@ def run(command_path = None):
command_path = command_path) command_path = command_path)
log.notice('RTEMS Testing - Tester, v%s' % (version.str())) log.notice('RTEMS Testing - Tester, v%s' % (version.str()))
if opts.find_arg('--list-bsps'): if opts.find_arg('--list-bsps'):
list_bsps(opts) bsps.list(opts)
exe_filter = opts.find_arg('--filter') exe_filter = opts.find_arg('--filter')
if exe_filter: if exe_filter:
exe_filter = exe_filter[1] exe_filter = exe_filter[1]
@@ -246,7 +255,6 @@ def run(command_path = None):
reporting = 1 reporting = 1
jobs = int(opts.jobs(opts.defaults['_ncpus'])) jobs = int(opts.jobs(opts.defaults['_ncpus']))
exe = 0 exe = 0
tests = []
finished = [] finished = []
if jobs > len(executables): if jobs > len(executables):
jobs = len(executables) jobs = len(executables)
@@ -288,7 +296,8 @@ def run(command_path = None):
report_finished(reports, report_mode, -1, finished, job_trace) report_finished(reports, report_mode, -1, finished, job_trace)
reports.summary() reports.summary()
end_time = datetime.datetime.now() end_time = datetime.datetime.now()
log.notice('Testing time: %s' % (str(end_time - start_time))) log.notice('Average test time: %s' % (str((end_time - start_time) / total)))
log.notice('Testing time : %s' % (str(end_time - start_time)))
except error.general, gerr: except error.general, gerr:
print gerr print gerr
sys.exit(1) sys.exit(1)
@@ -302,8 +311,9 @@ def run(command_path = None):
print '}} dumping:', threading.active_count() print '}} dumping:', threading.active_count()
for t in threading.enumerate(): for t in threading.enumerate():
print '}} ', t.name print '}} ', t.name
print stacktraces() print stacktraces.trace()
log.notice('abort: user terminated') log.notice('abort: user terminated')
killall(tests)
sys.exit(1) sys.exit(1)
finally: finally:
console.restore(stdtty) console.restore(stdtty)