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