13435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty#!/usr/bin/python
23435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
33435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty# Use of this source code is governed by a BSD-style license that can be
43435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty# found in the LICENSE file.
53435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
63435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty"""Report summarizer of internal test pass% from running many tests in LTP.
73435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
83435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike TrutyLTP is the Linux Test Project from http://ltp.sourceforge.net/.
93435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
103435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike TrutyThis script serves to summarize the results of a test run by LTP test
113435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyinfrastructure.  LTP frequently runs >1000 tests so summarizing the results
123435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyby result-type and count is useful. This script is invoked by the ltp.py
133435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutywrapper in Autotest as a post-processing step to summarize the LTP run results
143435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyin the Autotest log file.
153435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
163435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike TrutyThis script may be invoked by the command-line as follows:
173435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
183435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty$ ./parse_ltp_out.py -l /mypath/ltp.out
193435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty"""
203435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
213435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyimport optparse
223435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyimport os
233435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyimport re
243435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyimport sys
253435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
263435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
273435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike TrutySUMMARY_BORDER = 80 * '-'
283435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty# Prefix char used in summaries:
293435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty# +: sums into 'passing'
303435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty# -: sums into 'notpassing'
313435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike TrutyTEST_FILTERS = {'TPASS': '+Pass', 'TFAIL': '-Fail', 'TBROK': '-Broken',
323435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                'TCONF': '-Config error', 'TRETR': 'Retired',
333435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                'TWARN': '+Warning'}
343435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
353435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
363435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef parse_args(argv):
373435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """Setup command line parsing options.
383435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
393435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param argv: command-line arguments.
403435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
413435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @return parsed option result from optparse.
423435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
433435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    parser = optparse.OptionParser('Usage: %prog --ltp-out-file=/path/ltp.out')
443435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    parser.add_option(
453435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        '-l', '--ltp-out-file',
463435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        help='[required] Path and file name for ltp.out [default: %default]',
473435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        dest='ltp_out_file',
483435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        default=None)
493435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    parser.add_option(
503435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        '-t', '--timings',
513435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        help='Show test timings in buckets [default: %default]',
523435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        dest='test_timings', action='store_true',
533435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        default=False)
543435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    options, args = parser.parse_args()
553435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    if not options.ltp_out_file:
563435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        parser.error('You must supply a value for --ltp-out-file.')
573435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
583435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    return options
593435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
603435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
613435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef _filter_and_count(ltp_out_file, test_filters):
623435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """Utility function to count lines that match certain filters.
633435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
643435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param ltp_out_file: human-readable output file from LTP -p (ltp.out).
653435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param test_filters: dict of the tags to match and corresponding print tags.
663435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
673435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @return a dictionary with counts of the lines that matched each tag.
683435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
693435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    marker_line = '^<<<%s>>>$'
703435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    status_line_re = re.compile('^\w+ +\d+ +(\w+) +: +\w+')
713435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    filter_accumulator = dict.fromkeys(test_filters.keys(), 0)
723435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    parse_states = (
733435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        {'filters': {},
743435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty         'terminator': re.compile(marker_line % 'test_output')},
753435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        {'filters': filter_accumulator,
763435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty         'terminator': re.compile(marker_line % 'execution_status')})
773435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
783435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    # Simple 2-state state machine.
793435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    state_test_active = False
803435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    with open(ltp_out_file) as f:
813435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        for line in f:
823435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            state_index = int(state_test_active)
833435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            if re.match(parse_states[state_index]['terminator'], line):
843435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                # This state is terminated - proceed to next.
853435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                state_test_active = not state_test_active
863435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            else:
873435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                # Determine if this line matches any of the sought tags.
883435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                m = re.match(status_line_re, line)
893435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                if m and m.group(1) in parse_states[state_index]['filters']:
903435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                    parse_states[state_index]['filters'][m.group(1)] += 1
913435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    return filter_accumulator
923435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
933435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
943435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef _print_summary(filters, accumulator):
953435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """Utility function to print the summary of the parsing of ltp.out.
963435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
973435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    Prints a count of each type of test result, then a %pass-rate score.
983435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
993435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param filters: map of tags sought and corresponding print headers.
1003435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param accumulator: counts of test results with same keys as filters.
1013435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
1023435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1033435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print 'Linux Test Project (LTP) Run Summary:'
1043435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1053435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    # Size the header to the largest printable tag.
1063435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    fmt = '%%%ss: %%s' % max(map(lambda x: len(x), filters.values()))
1073435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    for k in sorted(filters):
1083435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty         print fmt % (filters[k], accumulator[k])
1093435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1103435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1113435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    # These calculations from ltprun-summary.sh script.
1123435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    pass_count = sum([accumulator[k] for k in filters if filters[k][0] == '+'])
1133435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    notpass_count = sum([accumulator[k] for k in filters
1143435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                                        if filters[k][0] == '-'])
1153435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    total_count = pass_count + notpass_count
1163435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    if total_count:
1173435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty      score = float(pass_count) / float(total_count) * 100.0
1183435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    else:
1193435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty      score = 0.0
1203435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print 'SCORE.ltp: %.2f' % score
1213435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1223435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1233435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1243435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef _filter_times(ltp_out_file):
1253435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """Utility function to count lines that match certain filters.
1263435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1273435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param ltp_out_file: human-readable output file from LTP -p (ltp.out).
1283435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1293435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @return a dictionary with test tags and corresponding times.
1303435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            The dictionary is a set of buckets of tests based on the test
1313435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            duration:
1323435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            0: [tests that recoreded 0sec runtimes],
1333435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            1: [tests that recorded runtimes from 0-60sec], ...
1343435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            2: [tests that recorded runtimes from 61-120sec], ...
1353435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
1363435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    test_tag_line_re = re.compile('^tag=(\w+)\s+stime=(\d+)$')
1373435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    test_duration_line_re = re.compile('^duration=(\d+)\s+.*')
1383435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    filter_accumulator = {}
1393435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    with open(ltp_out_file) as f:
1403435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        previous_tag = None
1413435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        previous_time_s = 0
1423435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        recorded_tags = set()
1433435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        for line in f:
1443435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            tag_matches = re.match(test_tag_line_re, line)
1453435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            if tag_matches:
1463435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                current_tag = tag_matches.group(1)
1473435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                if previous_tag:
1483435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                    if previous_tag in recorded_tags:
1493435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                        print 'WARNING: duplicate tag found: %s.' % previous_tag
1503435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                previous_tag = current_tag
1513435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                continue
1523435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            duration_matches = re.match(test_duration_line_re, line)
1533435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            if duration_matches:
1543435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                duration = int(duration_matches.group(1))
1553435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                if not previous_tag:
1563435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                    print 'WARNING: duration without a tag: %s.' % duration
1573435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                    continue
1583435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                if duration != 0:
1593435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                    duration = int(duration / 60) + 1
1603435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                test_list = filter_accumulator.setdefault(duration, [])
1613435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                test_list.append(previous_tag)
1623435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    return filter_accumulator
1633435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1643435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1653435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef _print_timings(accumulator):
1663435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """Utility function to print the summary of the parsing of ltp.out.
1673435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1683435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    Prints a count of each type of test result, then a %pass-rate score.
1693435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1703435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    Args:
1713435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param accumulator: counts of test results
1723435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
1733435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1743435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print 'Linux Test Project (LTP) Timing Summary:'
1753435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1763435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    for test_limit in sorted(accumulator.keys()):
1773435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        print '<=%smin: %s tags: %s' % (
1783435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            test_limit, len(accumulator[test_limit]),
1793435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty            ', '.join(sorted(accumulator[test_limit])))
1803435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        print ''
1813435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    print SUMMARY_BORDER
1823435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    return
1833435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1843435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1853435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef summarize(ltp_out_file, test_timings=None):
1863435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """Scan detailed output from LTP run for summary test status reporting.
1873435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1883435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    Looks for all possible test result types know to LTP: pass, fail, broken,
1893435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    config error, retired and warning.  Prints a summary.
1903435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1913435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param ltp_out_file: human-readable output file from LTP -p (ltp.out).
1923435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param test_timings: if True, emit an ordered summary of run timings of
1933435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty                         tests.
1943435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
1953435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    if not os.path.isfile(ltp_out_file):
1963435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        print 'Unable to locate %s.' % ltp_out_file
1973435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty        return
1983435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
1993435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    _print_summary(TEST_FILTERS, _filter_and_count(ltp_out_file, TEST_FILTERS))
2003435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    if test_timings:
2013435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty      _print_timings(_filter_times(ltp_out_file))
2023435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
2033435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
2043435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutydef main(argv):
2053435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """ Parse the human-readable logs from an LTP run and print a summary.
2063435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
2073435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    @param argv: command-line arguments.
2083435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    """
2093435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    options = parse_args(argv)
2103435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    summarize(options.ltp_out_file, options.test_timings)
2113435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
2123435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty
2133435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Trutyif __name__ == '__main__':
2143435de95fd6950d4bc2567bd1d19e6e54f73ecaeMike Truty    main(sys.argv)
215