diff options
Diffstat (limited to 'gdb/testsuite/analyze-racy-logs.py')
-rw-r--r-- | gdb/testsuite/analyze-racy-logs.py | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/gdb/testsuite/analyze-racy-logs.py b/gdb/testsuite/analyze-racy-logs.py new file mode 100644 index 0000000..06dbc3b --- /dev/null +++ b/gdb/testsuite/analyze-racy-logs.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python + +# Copyright (C) 2016 Free Software Foundation, Inc. +# +# This file is part of GDB. +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + + +# This program is used to analyze the test results (i.e., *.sum files) +# generated by GDB's testsuite, and print the testcases that are found +# to be racy. +# +# Racy testcases are considered as being testcases which can +# intermittently FAIL (or PASS) when run two or more times +# consecutively, i.e., tests whose results are not deterministic. +# +# This program is invoked when the user runs "make check" and +# specifies the RACY_ITER environment variable. + +import sys +import os +import re + +# The (global) dictionary that stores the associations between a *.sum +# file and its results. The data inside it will be stored as: +# +# files_and_tests = { 'file1.sum' : { 'PASS' : { 'test1', 'test2' ... }, +# 'FAIL' : { 'test5', 'test6' ... }, +# ... +# }, +# { 'file2.sum' : { 'PASS' : { 'test1', 'test3' ... }, +# ... +# } +# } + +files_and_tests = dict () + +# The relatioships between various states of the same tests that +# should be ignored. For example, if the same test PASSes on a +# testcase run but KFAILs on another, this test should be considered +# racy because a known-failure is... known. + +ignore_relations = { 'PASS' : 'KFAIL' } + +# We are interested in lines that start with '.?(PASS|FAIL)'. In +# other words, we don't process errors (maybe we should). + +sum_matcher = re.compile('^(.?(PASS|FAIL)): (.*)$') + +def parse_sum_line (line, dic): + """Parse a single LINE from a sumfile, and store the results in the +dictionary referenced by DIC.""" + global sum_matcher + + line = line.rstrip () + m = re.match (sum_matcher, line) + if m: + result = m.group (1) + test_name = m.group (3) + # Remove tail parentheses. These are likely to be '(timeout)' + # and other extra information that will only confuse us. + test_name = re.sub ('(\s+)?\(.*$', '', test_name) + if result not in dic.keys (): + dic[result] = set () + if test_name in dic[result]: + # If the line is already present in the dictionary, then + # we include a unique identifier in the end of it, in the + # form or '<<N>>' (where N is a number >= 2). This is + # useful because the GDB testsuite is full of non-unique + # test messages; however, if you process the racy summary + # file you will also need to perform this same operation + # in order to identify the racy test. + i = 2 + while True: + nname = test_name + ' <<' + str (i) + '>>' + if nname not in dic[result]: + break + i += 1 + test_name = nname + dic[result].add (test_name) + +def read_sum_files (files): + """Read the sumfiles (passed as a list in the FILES variable), and +process each one, filling the FILES_AND_TESTS global dictionary with +information about them. """ + global files_and_tests + + for x in files: + with open (x, 'r') as f: + files_and_tests[x] = dict () + for line in f.readlines (): + parse_sum_line (line, files_and_tests[x]) + +def identify_racy_tests (): + """Identify and print the racy tests. This function basically works +on sets, and the idea behind it is simple. It takes all the sets that +refer to the same result (for example, all the sets that contain PASS +tests), and compare them. If a test is present in all PASS sets, then +it is not racy. Otherwise, it is. + +This function does that for all sets (PASS, FAIL, KPASS, KFAIL, etc.), +and then print a sorted list (without duplicates) of all the tests +that were found to be racy.""" + global files_and_tests + + # First, construct two dictionaries that will hold one set of + # testcases for each state (PASS, FAIL, etc.). + # + # Each set in NONRACY_TESTS will contain only the non-racy + # testcases for that state. A non-racy testcase is a testcase + # that has the same state in all test runs. + # + # Each set in ALL_TESTS will contain all tests, racy or not, for + # that state. + nonracy_tests = dict () + all_tests = dict () + for f in files_and_tests: + for state in files_and_tests[f]: + try: + nonracy_tests[state] &= files_and_tests[f][state].copy () + except KeyError: + nonracy_tests[state] = files_and_tests[f][state].copy () + + try: + all_tests[state] |= files_and_tests[f][state].copy () + except KeyError: + all_tests[state] = files_and_tests[f][state].copy () + + # Now, we eliminate the tests that are present in states that need + # to be ignored. For example, tests both in the PASS and KFAIL + # states should not be considered racy. + ignored_tests = set () + for s1, s2 in ignore_relations.iteritems (): + try: + ignored_tests |= (all_tests[s1] & all_tests[s2]) + except: + continue + + racy_tests = set () + for f in files_and_tests: + for state in files_and_tests[f]: + racy_tests |= files_and_tests[f][state] - nonracy_tests[state] + + racy_tests = racy_tests - ignored_tests + + # Print the header. + print "\t\t=== gdb racy tests ===\n" + + # Print each test. + for line in sorted (racy_tests): + print line + + # Print the summary. + print "\n" + print "\t\t=== gdb Summary ===\n" + print "# of racy tests:\t\t%d" % len (racy_tests) + +if __name__ == '__main__': + if len (sys.argv) < 3: + # It only makes sense to invoke this program if you pass two + # or more files to be analyzed. + sys.exit ("Usage: %s [FILE] [FILE] ..." % sys.argv[0]) + read_sum_files (sys.argv[1:]) + identify_racy_tests () + exit (0) |