summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Johns <chrisj@rtems.org>2017-11-03 17:57:37 +1100
committerChris Johns <chrisj@rtems.org>2017-11-03 17:59:53 +1100
commitc68beb818173dee2134aac428b39ee2e558dd4f8 (patch)
tree0c838ea9992ad330876fbc0db4f07402920a83a4
parent7e8587817382cce44c506ff732b40914f8cd33d9 (diff)
tester: Add the rtems-run command.
-rw-r--r--rtemstoolkit/host.py15
-rw-r--r--tester/rt/config.py95
-rw-r--r--tester/rt/run.py175
-rw-r--r--tester/rt/test.py52
-rwxr-xr-xtester/rtems-run43
-rw-r--r--tester/wscript4
6 files changed, 314 insertions, 70 deletions
diff --git a/rtemstoolkit/host.py b/rtemstoolkit/host.py
index bc75394..ea23b24 100644
--- a/rtemstoolkit/host.py
+++ b/rtemstoolkit/host.py
@@ -97,8 +97,23 @@ def overrides():
_load()
return platform.overrides()
+def label(mode = 'all'):
+ import platform
+ if mode == 'system':
+ return platform.system()
+ compact = platform.platform(aliased = True)
+ if mode == 'compact':
+ return compact
+ extended = ' '.join(platform.uname())
+ if mode == 'extended':
+ return extended
+ if mode == 'all':
+ return '%s (%s)' % (compact, extended)
+ raise error.general('invalid platform mode: %s' % (mode))
+
if __name__ == '__main__':
import pprint
+ pprint.pprint(platform())
_load()
print('Name : %s' % (name))
if is_windows:
diff --git a/tester/rt/config.py b/tester/rt/config.py
index b1a9b79..8ed3d21 100644
--- a/tester/rt/config.py
+++ b/tester/rt/config.py
@@ -39,6 +39,7 @@ import os
import re
import threading
+from rtemstoolkit import configuration
from rtemstoolkit import config
from rtemstoolkit import error
from rtemstoolkit import execute
@@ -59,10 +60,13 @@ class file(config.file):
'%tftp',
'%console']
- def __init__(self, index, total, report, name, opts, _directives = _directives):
+ def __init__(self, index, total, report, name, opts,
+ console_prefix = '] ', _directives = _directives):
super(file, self).__init__(name, opts, directives = _directives)
self.lock = threading.Lock()
- self.realtime_trace = self.debug_trace('output')
+ self.realtime_trace = self.exe_trace('output')
+ self.console_trace = self.exe_trace('console')
+ self.console_prefix = console_prefix
self.process = None
self.console = None
self.output = None
@@ -182,9 +186,10 @@ class file(config.file):
return 0
def _capture_console(self, text):
- text = [('=>', l) for l in text.replace(chr(13), '').splitlines()]
+ text = [('=> ', l) for l in text.replace(chr(13), '').splitlines()]
if self.output is not None:
- self._realtime_trace(text)
+ if self.console_trace:
+ self._realtime_trace(text)
self.output += text
def _dir_console(self, data):
@@ -192,7 +197,7 @@ class file(config.file):
raise error.general(self._name_line_msg('console already configured'))
if len(data) == 0:
raise error.general(self._name_line_msg('no console configuration provided'))
- console_trace = trace = self.debug_trace('console')
+ console_trace = trace = self.exe_trace('console')
if not self.opts.dry_run():
if data[0] == 'stdio':
self.console = console.stdio(trace = console_trace)
@@ -233,8 +238,8 @@ class file(config.file):
if len(data) < 3 or len(data) > 4:
raise error.general('invalid %gdb arguments')
self.process = gdb.gdb(bsp_arch, bsp,
- trace = self.debug_trace('gdb'),
- mi_trace = self.debug_trace('gdb-mi'))
+ trace = self.exe_trace('gdb'),
+ mi_trace = self.exe_trace('gdb-mi'))
script = self.expand('%%{%s}' % data[2])
if script:
script = [l.strip() for l in script.splitlines()]
@@ -260,7 +265,7 @@ class file(config.file):
self.kill_on_end = True
if not self.opts.dry_run():
self.process = tftp.tftp(bsp_arch, bsp,
- trace = self.debug_trace('tftp'))
+ trace = self.exe_trace('tftp'))
if not self.in_error:
if self.console:
self.console.open()
@@ -296,7 +301,8 @@ class file(config.file):
bsp_arch = self.expand('%{arch}')
bsp = self.expand('%{bsp}')
fexe = self._target_exe_filter(exe)
- self.report.start(index, total, exe, fexe, bsp_arch, bsp)
+ if self.report is not None:
+ self.report.start(index, total, exe, fexe, bsp_arch, bsp)
if self.index == 1:
self._target_command('on', bsp_arch, bsp, exe, fexe)
self._target_command('pretest', bsp_arch, bsp, exe, fexe)
@@ -315,12 +321,12 @@ class file(config.file):
self._target_command('off', bsp_arch, bsp, exe, fexe)
self._target_command('posttest', bsp_arch, bsp, exe, fexe)
try:
- status = self.report.end(exe, self.output)
- self._capture_console('test result: %s' % (status))
+ status = ''
+ if self.report is not None:
+ status = self.report.end(exe, self.output)
+ self._capture_console('test result: %s' % (status))
if status == 'timeout':
- if self.index == self.total:
- self._target_command('off', bsp_arch, bsp, exe, fexe)
- else:
+ if self.index != self.total:
self._target_command('reset', bsp_arch, bsp, exe, fexe)
self.process = None
self.output = None
@@ -329,9 +335,8 @@ class file(config.file):
return None, None, None
def _realtime_trace(self, text):
- if self.realtime_trace:
- for l in text:
- print(' '.join(l))
+ for l in text:
+ print(''.join(l))
def run(self):
self.target_start_regx = self._target_regex('target_start_regex')
@@ -360,16 +365,17 @@ class file(config.file):
if not reset_target and self.target_reset_regx is not None:
if self.target_reset_regx.match(text):
self.capture_console('target reset condition detected')
- reset_target = True
+ self._target_command('reset')
if self.kill_on_end:
if not ok_to_kill and '*** END OF TEST ' in text:
self.capture_console('test end: %s' % (self.test_label))
if self.test_label is not None:
ok_to_kill = '*** END OF TEST %s ***' % (self.test_label) in text
- text = [(']', l) for l in text.replace(chr(13), '').splitlines()]
+ text = [(self.console_prefix, l) for l in text.replace(chr(13), '').splitlines()]
self._lock()
if self.output is not None:
- self._realtime_trace(text)
+ if self.realtime_trace:
+ self._realtime_trace(text)
self.output += text
if reset_target:
if self.index == self.total:
@@ -385,8 +391,8 @@ class file(config.file):
self._capture_console(text)
self._unlock()
- def debug_trace(self, flag):
- dt = self.macros['debug_trace']
+ def exe_trace(self, flag):
+ dt = self.macros['exe_trace']
if dt:
if flag in dt.split(','):
return True
@@ -398,3 +404,48 @@ class file(config.file):
self.process.kill()
except:
pass
+
+def load(bsp, opts):
+ mandatory = ['bsp', 'arch', 'tester']
+ cfg = configuration.configuration()
+ path_ = opts.defaults.expand('%%{_configdir}/bsps/%s.ini' % (bsp))
+ ini_name = path.basename(path_)
+ for p in path.dirname(path_).split(':'):
+ if path.exists(path.join(p, ini_name)):
+ cfg.load(path.join(p, ini_name))
+ if not cfg.has_section(bsp):
+ raise error.general('bsp section not found in ini: [%s]' % (bsp))
+ item_names = cfg.get_item_names(bsp, err = False)
+ for m in mandatory:
+ if m not in item_names:
+ raise error.general('mandatory item not found in bsp section: %s' % (m))
+ opts.defaults.set_write_map(bsp, add = True)
+ for i in cfg.get_items(bsp, flatten = False):
+ opts.defaults[i[0]] = i[1]
+ if not opts.defaults.set_read_map(bsp):
+ raise error.general('cannot set BSP read map: %s' % (bsp))
+ # Get a copy of the required fields we need
+ requires = cfg.comma_list(bsp, 'requires', err = False)
+ del cfg
+ user_config = opts.find_arg('--user-config')
+ if user_config is not None:
+ user_config = path.expanduser(user_config[1])
+ if not path.exists(user_config):
+ raise error.general('cannot find user configuration file: %s' % (user_config))
+ else:
+ if 'HOME' in os.environ:
+ user_config = path.join(os.environ['HOME'], '.rtemstesterrc')
+ if user_config:
+ if path.exists(user_config):
+ cfg = configuration.configuration()
+ cfg.load(user_config)
+ if cfg.has_section(bsp):
+ for i in cfg.get_items(bsp, flatten = False):
+ opts.defaults[i[0]] = i[1]
+ # Check for the required values.
+ for r in requires:
+ if opts.defaults.get(r) is None:
+ raise error.general('user value missing, BSP %s requires \'%s\': missing: %s' % \
+ (bsp, ', '.join(requires), r))
+ return opts.defaults['bsp']
+ raise error.general('cannot find bsp configuration file: %s.ini' % (bsp))
diff --git a/tester/rt/run.py b/tester/rt/run.py
new file mode 100644
index 0000000..bcd0697
--- /dev/null
+++ b/tester/rt/run.py
@@ -0,0 +1,175 @@
+#
+# RTEMS Tools Project (http://www.rtems.org/)
+# Copyright 2013-2017 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 __future__ import print_function
+
+import copy
+import datetime
+import fnmatch
+import os
+import re
+import sys
+import threading
+import time
+
+from rtemstoolkit import error
+from rtemstoolkit import host
+from rtemstoolkit import log
+from rtemstoolkit import path
+from rtemstoolkit import stacktraces
+from rtemstoolkit import version
+
+from . import bsps
+from . import config
+from . import console
+from . import options
+from . import report
+
+class test(object):
+ def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
+ self.index = index
+ self.total = total
+ self.report = report
+ self.bsp = bsp
+ self.bsp_config = bsp_config
+ self.opts = copy.copy(opts)
+ self.opts.defaults['test_index'] = str(index)
+ self.opts.defaults['test_total'] = str(total)
+ self.opts.defaults['bsp'] = bsp
+ self.opts.defaults['bsp_arch'] = '%{arch}'
+ if not path.isfile(executable):
+ raise error.general('cannot find executable: %s' % (executable))
+ self.opts.defaults['test_executable'] = executable
+ if rtems_tools:
+ rtems_tools_bin = path.join(self.opts.defaults.expand(rtems_tools), 'bin')
+ if not path.isdir(rtems_tools_bin):
+ raise error.general('cannot find RTEMS tools path: %s' % (rtems_tools_bin))
+ self.opts.defaults['rtems_tools'] = rtems_tools_bin
+ self.config = config.file(index, total, 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()
+
+def find_executables(files):
+ executables = []
+ for f in files:
+ if not path.isfile(f):
+ raise error.general('executable is not a file: %s' % (f))
+ executables += [f]
+ return sorted(executables)
+
+def list_bsps(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()
+
+def run(command_path = None):
+ import sys
+ tests = []
+ stdtty = console.save()
+ opts = None
+ default_exefilter = '*.exe'
+ try:
+ optargs = { '--rtems-tools': 'The path to the RTEMS tools',
+ '--rtems-bsp': 'The RTEMS BSP to run the test on',
+ '--user-config': 'Path to your local user configuration INI file',
+ '--list-bsps': 'List the supported BSPs',
+ '--debug-trace': 'Debug trace based on specific flags',
+ '--stacktrace': 'Dump a stack trace on a user termination (^C)' }
+ opts = options.load(sys.argv,
+ optargs = optargs,
+ command_path = command_path)
+ log.notice('RTEMS Testing - Run, %s' % (version.str()))
+ if opts.find_arg('--list-bsps'):
+ bsps.list(opts)
+ opts.log_info()
+ log.output('Host: ' + host.label(mode = 'all'))
+ debug_trace = opts.find_arg('--debug-trace')
+ if debug_trace:
+ if len(debug_trace) != 1:
+ debug_trace = 'output,' + debug_trace[1]
+ else:
+ raise error.general('no debug flags, can be: console,gdb,output')
+ else:
+ debug_trace = 'output'
+ opts.defaults['debug_trace'] = debug_trace
+ rtems_tools = opts.find_arg('--rtems-tools')
+ if rtems_tools:
+ if len(rtems_tools) != 2:
+ raise error.general('invalid RTEMS tools option')
+ rtems_tools = rtems_tools[1]
+ else:
+ rtems_tools = '%{_prefix}'
+ bsp = opts.find_arg('--rtems-bsp')
+ if bsp is None or len(bsp) != 2:
+ raise error.general('RTEMS BSP not provided or an invalid option')
+ bsp = config.load(bsp[1], opts)
+ bsp_config = opts.defaults.expand(opts.defaults['tester'])
+ executables = find_executables(opts.params())
+ if len(executables) != 1:
+ raise error.general('one executable required, found %d' % (len(executables)))
+ start_time = datetime.datetime.now()
+ opts.defaults['exe_trace'] = debug_trace
+ tst = test(1, 1, None, executables[0], rtems_tools, bsp, bsp_config, opts)
+ tst.run()
+ end_time = datetime.datetime.now()
+ total_time = 'Run time : %s' % (str(end_time - start_time))
+ log.notice(total_time)
+
+ except error.general as gerr:
+ print(gerr)
+ sys.exit(1)
+ except error.internal as ierr:
+ print(ierr)
+ sys.exit(1)
+ except error.exit:
+ sys.exit(2)
+ except KeyboardInterrupt:
+ if opts is not None and opts.find_arg('--stacktrace'):
+ print('}} dumping:', threading.active_count())
+ for t in threading.enumerate():
+ print('}} ', t.name)
+ print(stacktraces.trace())
+ log.notice('abort: user terminated')
+ sys.exit(1)
+ finally:
+ console.restore(stdtty)
+ sys.exit(0)
+
+if __name__ == "__main__":
+ run()
diff --git a/tester/rt/test.py b/tester/rt/test.py
index bb6d627..70ecf6b 100644
--- a/tester/rt/test.py
+++ b/tester/rt/test.py
@@ -41,6 +41,7 @@ import time
from rtemstoolkit import configuration
from rtemstoolkit import error
+from rtemstoolkit import host
from rtemstoolkit import log
from rtemstoolkit import path
from rtemstoolkit import mailer
@@ -198,51 +199,6 @@ def report_finished(reports, report_mode, reporting, finished, job_trace):
print('}} ', t.name)
return reporting
-def load_configuration(bsp, opts):
- mandatory = ['bsp', 'arch', 'tester']
- cfg = configuration.configuration()
- path_ = opts.defaults.expand('%%{_configdir}/bsps/%s.ini' % (bsp))
- ini_name = path.basename(path_)
- for p in path.dirname(path_).split(':'):
- if path.exists(path.join(p, ini_name)):
- cfg.load(path.join(p, ini_name))
- if not cfg.has_section(bsp):
- raise error.general('bsp section not found in ini: [%s]' % (bsp))
- item_names = cfg.get_item_names(bsp, err = False)
- for m in mandatory:
- if m not in item_names:
- raise error.general('mandatory item not found in bsp section: %s' % (m))
- opts.defaults.set_write_map(bsp, add = True)
- for i in cfg.get_items(bsp, flatten = False):
- opts.defaults[i[0]] = i[1]
- if not opts.defaults.set_read_map(bsp):
- raise error.general('cannot set BSP read map: %s' % (bsp))
- # Get a copy of the required fields we need
- requires = cfg.comma_list(bsp, 'requires', err = False)
- del cfg
- user_config = opts.find_arg('--user-config')
- if user_config is not None:
- user_config = path.expanduser(user_config[1])
- if not path.exists(user_config):
- raise error.general('cannot find user configuration file: %s' % (user_config))
- else:
- if 'HOME' in os.environ:
- user_config = path.join(os.environ['HOME'], '.rtemstesterrc')
- if user_config:
- if path.exists(user_config):
- cfg = configuration.configuration()
- cfg.load(user_config)
- if cfg.has_section(bsp):
- for i in cfg.get_items(bsp, flatten = False):
- opts.defaults[i[0]] = i[1]
- # Check for the required values.
- for r in requires:
- if opts.defaults.get(r) is None:
- raise error.general('user value missing, BSP %s requires: %s' % \
- (bsp, ', '.join(requires)))
- return opts.defaults['bsp']
- raise error.general('cannot find bsp configuration file: %s.ini' % (bsp))
-
def _job_trace(tst, msg, total, exe, active, reporting):
s = ''
for a in active:
@@ -306,6 +262,7 @@ def run(command_path = None):
else:
exe_filter = default_exefilter
opts.log_info()
+ log.output('Host: ' + host.label(mode = 'all'))
debug_trace = opts.find_arg('--debug-trace')
if debug_trace:
if len(debug_trace) != 1:
@@ -314,7 +271,7 @@ def run(command_path = None):
raise error.general('no debug flags, can be: console,gdb,output')
else:
debug_trace = ''
- opts.defaults['debug_trace'] = debug_trace
+ opts.defaults['exe_trace'] = debug_trace
job_trace = 'jobs' in debug_trace.split(',')
rtems_tools = opts.find_arg('--rtems-tools')
if rtems_tools:
@@ -326,7 +283,7 @@ def run(command_path = None):
bsp = opts.find_arg('--rtems-bsp')
if bsp is None or len(bsp) != 2:
raise error.general('RTEMS BSP not provided or an invalid option')
- bsp = load_configuration(bsp[1], opts)
+ bsp = coinfig.load(bsp[1], opts)
bsp_config = opts.defaults.expand(opts.defaults['tester'])
report_mode = opts.find_arg('--report-mode')
if report_mode:
@@ -394,6 +351,7 @@ def run(command_path = None):
if mail is not None and output is not None:
subject = '[rtems-test] %s: %s' % (str(start_time).split('.')[0], bsp)
body = [total_time, average_time,
+ '', 'Host', '====', host.label(mode = 'all'),
'', 'Summary', '=======', '',
reports.score_card(), '',
reports.failures(),
diff --git a/tester/rtems-run b/tester/rtems-run
new file mode 100755
index 0000000..d6f2f56
--- /dev/null
+++ b/tester/rtems-run
@@ -0,0 +1,43 @@
+#! /usr/bin/env python
+#
+# RTEMS Tools Project (http://www.rtems.org/)
+# Copyright 2017 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, os
+base = os.path.dirname(os.path.abspath(sys.argv[0]))
+parent = os.path.dirname(base)
+rtems = os.path.join(parent, 'share', 'rtems')
+sys.path = [parent, rtems, os.path.join(rtems, 'tester')] + sys.path
+
+try:
+ import rt.run
+ rt.run.run()
+except ImportError:
+ print >> sys.stderr, "Incorrect RTEMS Tools installation"
+ sys.exit(1)
diff --git a/tester/wscript b/tester/wscript
index 59eadc2..24d54b3 100644
--- a/tester/wscript
+++ b/tester/wscript
@@ -59,6 +59,7 @@ def build(bld):
'rt/gdb.py',
'rt/options.py',
'rt/report.py',
+ 'rt/run.py',
'rt/stty.py',
'rt/telnet.py',
'rt/test.py',
@@ -83,7 +84,8 @@ def build(bld):
install_from = '.',
install_path = '${PREFIX}/share/rtems/tester')
bld.install_files('${PREFIX}/bin',
- ['rtems-test',
+ ['rtems-run',
+ 'rtems-test',
'rtems-bsp-builder'],
chmod = 0o755)