--- a
+++ b/execltp.in
@@ -0,0 +1,468 @@
+#!/usr/bin/env python
+"""
+    An LTP [execution and] parsing wrapper.
+
+    Used as a second layer for ease-of-use with users as many developers
+    complain about complexity involved with trying to use LTP in my
+    organization -_-.
+
+    Copyright (C) 2009, Garrett Cooper
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+
+from optparse import OptionGroup, OptionParser
+import os, re, sys
+
+class ResultsParseException(Exception):
+    """ Extended class for parsing LTP results. """
+
+def parse_ltp_results(exec_log, output_log, verbose=0):
+    """ Function for parsing LTP results.
+
+    1. The exec log is the log with the results in summary form.
+
+    And now a note from our sponsors about exec logs...
+
+    startup='Thu Oct  1 06:42:07 2009'
+    tag=abort01 stime=1254379327 dur=2 exit=exited stat=0 core=no cu=0 cs=16
+    tag=accept01 stime=1254379329 dur=0 exit=exited stat=0 core=no cu=1 cs=0
+    tag=access01 stime=1254379329 dur=0 exit=exited stat=0 core=no cu=0 cs=0
+    tag=access02 stime=1254379329 dur=0 exit=exited stat=0 core=no cu=0 cs=0
+    tag=access03 stime=1254379329 dur=1 exit=exited stat=0 core=no cu=0 cs=1
+
+    [...]
+
+    a. tag is the test tag name.
+    b. stime is the system time at the start of the exec.
+    c. dur is the total duration of the test.
+    d. exit tells you what the result was. Valid values are:
+       - exited
+       - signaled
+       - stopped
+       - unknown
+       See run_child in pan.c.
+    e. stat is the exit status.
+    f. core answers the question: `did I dump core?'.
+    g. cu is the cutime (cumulative user time).
+    h. cs is the cstime (cumulative system time).
+
+    2. The output log is the log with all of the terse results.
+    3. verbose tells us whether or not we need to include the passed results.
+    """
+
+    if not os.access(exec_log, os.R_OK):
+        raise ResultsParseException("Exec log - %s - specified doesn't exist"
+                                    % exec_log)
+    elif 1 < verbose and not os.access(output_log, os.R_OK):
+        # Need the output log for context to the end user.
+        raise ResultsParseException("Output log - %s - specified doesn't exist"
+                                    % output_log )
+
+    context = None
+
+    failed = [ ] 
+    passed = 0
+
+    if 2 <= verbose:
+        passed = [ ]
+
+    target_vals = ( 'exited', '0', 'no' )
+
+    fd = open(exec_log, 'r')
+
+    try:
+        content = fd.read()
+        matches = re.finditer('tag=(?P<tag>\w+).+exit=(?P<exit>\w+) '
+                              'stat=(?P<stat>\d+) core=(?P<core>\w+)', content)
+    finally:
+        fd.close()
+
+    if not matches:
+        raise ResultsParseException("No parseable results were found in the "
+                                    "exec log - `%s'."% exec_log)
+
+    for match in matches:
+
+        if ((match.group('exit'), match.group('stat'), match.group('core')) !=
+             target_vals):
+            failed.append(match.group('tag'))
+        elif 2 <= verbose:
+            passed.append(match.group('tag'))
+        else:
+            passed += 1
+
+    # Save memory on large files because lists can eat up a fair amount of
+    # memory.
+    matches = None
+
+    if 1 <= verbose:
+
+        context = { }
+
+        search_tags = failed[:]
+
+        if 2 <= verbose:
+            search_tags += passed
+
+        search_tags.sort()
+
+        fd = open(output_log, 'r')
+
+        try:
+
+            try:
+                lines = fd.readlines()
+            finally:
+                fd.close()
+
+            fd.close()
+
+            end_output = '<<<execution_status>>>'
+            output_start = '<<<test_output>>>'
+
+            tag_re = re.compile('tag=(\w+)')
+
+            grab_output = False
+
+            i = 0
+
+            local_context = ''
+
+            line_len = len(lines)
+
+            search_tag = None
+
+            while i < line_len:
+
+                if lines[i].startswith(end_output):
+
+                    if search_tag:
+                        context[search_tag] = local_context
+
+                    grab_output = False
+                    local_context = ''
+                    search_tag = None
+
+                if not search_tag:
+
+                    while i < len(lines):
+
+                        match = tag_re.match(lines[i])
+
+                        if match and match.group(1) in search_tags:
+                            search_tag = match.group(1)
+                            break
+
+                        i += 1
+
+                elif lines[i].startswith(output_start):
+                    grab_output = True
+                elif grab_output:
+                    local_context += lines[i]
+
+                i += 1
+
+            for k in context.keys():
+                if k not in search_tags:
+                    raise ResultsParseException('Leftover token in search '
+                                                'keys: %s' % k)
+
+        except Exception, exc:
+            # XXX (garrcoop): change from Exception to soft error and print
+            # out warning with logging module.
+            raise ResultsParseException('Encountered exception reading output '
+                                        'for context: %s' % str(exc))
+
+    return failed, passed, context
+
+def determine_context(output_log, testsuite, test_set, context):
+    """ Return a set of context values mapping test_set -> context. """
+
+    test_set_context = {}
+
+    for test in test_set:
+
+        if test in context:
+            test_context = context[test]
+            del context[test]
+        else:
+            test_context = ('Could not determine context for %s; please see '
+                            'output log - %s' % (test, output_log))
+
+
+        test_set_context['%s : %s' % (testsuite, test)] = test_context
+
+    return test_set_context
+
+def print_context(output_dest, header, testsuite_context):
+    """ Print out testsuite_context to output_dest, heading it up with
+        header.
+    """
+    output_dest.write('\n'.join(['', '=' * 40, header, '-' * 40, '']))
+
+    for test, context in testsuite_context.items():
+        output_dest.write('<output test="%s">\n%s\n</output>\n' %
+                          (test, context.strip()))
+
+def main():
+    """ main. """
+
+    parser = OptionParser(prog=os.path.basename(sys.argv[0]),
+                          usage='usage: %prog [options] test ...',
+                          version='0.0.1')
+
+    ltpdir = os.getenv('LTPROOT', '@prefix@')
+
+    parser.add_option('-l', '--ltp-dir', dest='ltp_dir',
+                      default=ltpdir, help='LTP directory [default: %default]')
+    parser.add_option('-L', '--log-dir', dest='log_dir',
+                      default=None,
+                      help=('directory for [storing and] retrieving logs '
+                            '[default: %s/output]' % ltpdir),
+                      metavar='DIR')
+    parser.add_option('-p', '--postprocess-only', dest='postprocess_only',
+                      default=False, action='store_true',
+                      help=("Don't execute runltp; just postprocess logs "
+                            "[default: %default]."))
+    parser.add_option('-o', '--output-file', dest='output_file',
+                      default=None,
+                      help='File to output results')
+    parser.add_option('-r', '--runltp-opts', dest='runltp_opts',
+                      default='',
+                      help=('options to pass directly to runltp (will '
+                            'suppress -q).'))
+
+    group = OptionGroup(parser, 'Logging',
+                        'If --summary-mode is 0, then the summary output is '
+                        'suppressed. '
+                        'If --summary-mode is 1 [the default], then summary '
+                        'output will be displayed for test execution'
+                        'If --summary-mode is 2, then summary output will be '
+                        'provided on a per-test suite basis. If only '
+                        'one test suite is specified, this has the same net '
+                        "effect as `--summary-mode 1'"
+                        'If --verbose is specified once, prints out failed '
+                        'test information with additional context. '
+                        'If --verbose is specified twice, prints out the '
+                        'failed and passed test context, as well as the '
+                        'summary.')
+
+    parser.add_option('-s', '--summary-mode', dest='summary_mode', default=1,
+                      type='int',
+                      help='See Logging.')
+
+    parser.add_option('-v', '--verbose', dest='verbose', default=0,
+                      action='count',
+                      help=('Increases context verbosity from tests. See '
+                            'Verbosity for more details.'))
+    parser.add_option_group(group)
+
+    group = OptionGroup(parser, 'Copyright',
+                        '%(prog)s version %(version)s, Copyright (C) 2009, '
+                        'Garrett Cooper %(prog)s comes with ABSOLUTELY NO '
+                        'WARRANTY; '
+                        'This is free software, and you are welcome to '
+                        'redistribute it under certain conditions (See the '
+                        'license tort in %(file)s for more details).'
+                        % { 'file'    : os.path.abspath(__file__),
+                            'prog'    : parser.prog,
+                            'version' : parser.version })
+
+    parser.add_option_group(group)
+
+    opts, args = parser.parse_args()
+
+    # Remove -q from the opts string, as long as it's a standalone option. 
+    runltp_opts = re.sub('^((?<!\S)+\-q\s+|\-q|\s+\-q(?!\S))$', '',
+                         opts.runltp_opts)
+
+    if not opts.log_dir:
+        opts.log_dir = os.path.join(opts.ltp_dir, 'output')    
+
+    if not opts.summary_mode and not opts.verbose:
+        parser.error('You cannot suppress summary output and disable '
+                     'verbosity.')
+    elif opts.summary_mode not in range(3):
+        parser.error('--summary-mode must be a value between 0 and 2.')
+
+    if len(args) == 0:
+        # This matches the default test suite list in runltp when -f isn't
+        # specified. Look for `SCENFILES'.
+        args = [ 'syscalls', 'fs', 'fsx', 'dio', 'io', 'mm', 'ipc', 'sched',
+                 'math', 'nptl', 'pty', 'containers', 'fs_bind', 'controllers',
+                 'filecaps', 'cap_bounds', 'fcntl-locktests', 'connectors',
+                 'admin_tools', 'timers', 'power_management_tests', 'numa',
+                 'hugetlb', 'commands', 'hyperthreading' ]
+
+    if opts.output_file:
+
+        output_dir = os.path.dirname(opts.output_file)
+
+        if output_dir:
+            # Not cwd; let's check to make sure that the directory does or
+            # does not exist.
+
+            if not os.path.exists(output_dir):
+                # We need to make the directory.
+                os.makedirs(os.path.dirname(opts.output_file))
+            elif not os.path.isdir(os.path.abspath(output_dir)):
+                # Path exists, but isn't a file. Oops!
+                parser.error('Dirname for path specified - %s - is not valid'
+                             % output_dir)
+
+        else:
+            # Current path (cwd)
+            opts.output_file = os.path.join(os.getcwd(), opts.output_file)
+
+        output_dest = open(opts.output_file, 'w')
+
+    else:
+
+        output_dest = sys.stdout
+
+    try:
+
+        failed_context = {}
+        passed_context = {}
+
+        failed_count = 0
+        passed_count = 0
+
+        if opts.summary_mode == 2 and len(args) == 1:
+            opts.summary_mode = 1
+
+        for testsuite in args:
+
+            # Iterate over the provided test list
+
+            context = {}
+            exec_log = os.path.join(opts.log_dir, '%s-exec.log' % testsuite)
+            output_log = os.path.join(opts.log_dir, ('%s-output.log'
+                                                     % testsuite))
+
+            failed_subset = {}
+
+            runtest_file = os.path.join(opts.ltp_dir, 'runtest', testsuite)
+
+            if not opts.postprocess_only:
+
+                for log in [ exec_log, output_log ]:
+                    if os.path.isfile(log):
+                        os.remove(log)
+
+                if not os.access(runtest_file, os.R_OK):
+                    output_dest.write("%s doesn't exist; skipping "
+                                      "test\n" % runtest_file)
+                    continue
+
+                os.system(' '.join([ os.path.join(opts.ltp_dir, 'runltp'),
+                                     runltp_opts, '-f', testsuite,
+                                     '-l', exec_log, '-o', output_log ]))
+
+            try:
+
+                failed_subset, passed_css, context = \
+                    parse_ltp_results(exec_log, output_log,
+                                  verbose=opts.verbose)
+
+            except ResultsParseException, rpe:
+                output_dest.write('Error encountered when parsing results for '
+                                  'test - %s: %s\n' % (testsuite, str(rpe)))
+                continue
+
+            failed_count += len(failed_subset)
+
+            failed_subset_context = {}
+            passed_subset_context = {}
+
+            if opts.verbose:
+                failed_subset_context = determine_context(output_log,
+                                                          testsuite,
+                                                          failed_subset,
+                                                          context)
+            if type(passed_css) == list:
+
+                passed_count += len(passed_css)
+
+                if opts.verbose == 2:
+                    passed_subset_context = determine_context(output_log,
+                                                              testsuite,
+                                                              passed_css,
+                                                              context)
+
+            else:
+
+                passed_count += passed_css
+
+            if opts.summary_mode == 1:
+
+                failed_context.update(failed_subset_context)
+                passed_context.update(passed_subset_context)
+
+            else:
+
+                if 1 <= opts.verbose:
+                    # Print out failed testcases.
+                    print_context(output_dest,
+                                  'FAILED TESTCASES for %s' % testsuite,
+                                  failed_subset_context)
+
+                if opts.verbose == 2:
+                    # Print out passed testcases with context.
+                    print_context(output_dest,
+                                  'PASSED TESTCASES for %s' % testsuite,
+                                  passed_subset_context)
+
+                if opts.summary_mode == 2:
+                    output_dest.write("""
+========================================
+SUMMARY for: %s
+----------------------------------------
+PASS - %d
+FAIL - %d
+----------------------------------------
+""" % (testsuite, passed_count, len(failed_subset)))
+
+        if opts.summary_mode == 1:
+
+            # Print out overall results.
+
+            if 1 <= opts.verbose:
+                # Print out failed testcases with context.
+                print_context(output_dest, "FAILED TESTCASES", failed_context)
+
+            if opts.verbose == 2:
+                # Print out passed testcases with context.
+                print_context(output_dest, "PASSED TESTCASES", passed_context)
+
+            output_dest.write("""
+========================================
+SUMMARY for tests:
+%s
+----------------------------------------
+PASS - %d
+FAIL - %d
+----------------------------------------
+""" % (' '.join(args), passed_count, failed_count))
+
+    finally:
+
+        if output_dest != sys.stdout:
+
+            output_dest.close()
+
+if __name__ == '__main__':
+    main()