|  | #!/usr/bin/env python3 | 
|  |  | 
|  | # Copyright (C) 2016-2022 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.items(): | 
|  | 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) |