Index: rtemstoolkit/host.py
===================================================================
--- rtemstoolkit/host.py	(revision 7e8587817382cce44c506ff732b40914f8cd33d9)
+++ rtemstoolkit/host.py	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
@@ -98,6 +98,21 @@
     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))
Index: tester/rt/config.py
===================================================================
--- tester/rt/config.py	(revision 7e8587817382cce44c506ff732b40914f8cd33d9)
+++ tester/rt/config.py	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
@@ -40,4 +40,5 @@
 import threading
 
+from rtemstoolkit import configuration
 from rtemstoolkit import config
 from rtemstoolkit import error
@@ -60,8 +61,11 @@
                    '%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
@@ -183,7 +187,8 @@
 
     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
 
@@ -193,5 +198,5 @@
         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':
@@ -234,6 +239,6 @@
             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:
@@ -261,5 +266,5 @@
         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:
@@ -297,5 +302,6 @@
                     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)
@@ -316,10 +322,10 @@
                 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
@@ -330,7 +336,6 @@
 
     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):
@@ -361,5 +366,5 @@
             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:
@@ -367,8 +372,9 @@
                 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:
@@ -386,6 +392,6 @@
         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(','):
@@ -399,2 +405,47 @@
             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))
Index: tester/rt/run.py
===================================================================
--- tester/rt/run.py	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
+++ tester/rt/run.py	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
@@ -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()
Index: tester/rt/test.py
===================================================================
--- tester/rt/test.py	(revision 7e8587817382cce44c506ff732b40914f8cd33d9)
+++ tester/rt/test.py	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
@@ -42,4 +42,5 @@
 from rtemstoolkit import configuration
 from rtemstoolkit import error
+from rtemstoolkit import host
 from rtemstoolkit import log
 from rtemstoolkit import path
@@ -198,49 +199,4 @@
                     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):
@@ -307,4 +263,5 @@
             exe_filter = default_exefilter
         opts.log_info()
+        log.output('Host: ' + host.label(mode = 'all'))
         debug_trace = opts.find_arg('--debug-trace')
         if debug_trace:
@@ -315,5 +272,5 @@
         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')
@@ -327,5 +284,5 @@
         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')
@@ -395,4 +352,5 @@
             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(), '',
Index: tester/rtems-run
===================================================================
--- tester/rtems-run	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
+++ tester/rtems-run	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
@@ -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)
Index: tester/wscript
===================================================================
--- tester/wscript	(revision 7e8587817382cce44c506ff732b40914f8cd33d9)
+++ tester/wscript	(revision c68beb818173dee2134aac428b39ee2e558dd4f8)
@@ -60,4 +60,5 @@
                   'rt/options.py',
                   'rt/report.py',
+                  'rt/run.py',
                   'rt/stty.py',
                   'rt/telnet.py',
@@ -84,5 +85,6 @@
         install_path = '${PREFIX}/share/rtems/tester')
     bld.install_files('${PREFIX}/bin',
-                      ['rtems-test',
+                      ['rtems-run',
+                       'rtems-test',
                        'rtems-bsp-builder'],
                       chmod = 0o755)
