aboutsummaryrefslogtreecommitdiff
path: root/gdb/testsuite/analyze-racy-logs.py
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/testsuite/analyze-racy-logs.py')
-rw-r--r--gdb/testsuite/analyze-racy-logs.py177
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)