diff options
author | Chris Johns <chrisj@rtems.org> | 2017-11-03 17:57:37 +1100 |
---|---|---|
committer | Chris Johns <chrisj@rtems.org> | 2017-11-03 17:59:53 +1100 |
commit | c68beb818173dee2134aac428b39ee2e558dd4f8 (patch) | |
tree | 0c838ea9992ad330876fbc0db4f07402920a83a4 | |
parent | tester: Make the mandatory items appear first. (diff) | |
download | rtems-tools-c68beb818173dee2134aac428b39ee2e558dd4f8.tar.bz2 |
tester: Add the rtems-run command.
-rw-r--r-- | rtemstoolkit/host.py | 15 | ||||
-rw-r--r-- | tester/rt/config.py | 95 | ||||
-rw-r--r-- | tester/rt/run.py | 175 | ||||
-rw-r--r-- | tester/rt/test.py | 52 | ||||
-rwxr-xr-x | tester/rtems-run | 43 | ||||
-rw-r--r-- | tester/wscript | 4 |
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) |