Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright 2017 The Chromium Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Runs all permutations of pairs of tests in a gtest binary to attempt to |
| 7 | detect state leakage between tests. |
| 8 | |
| 9 | Example invocation: |
| 10 | |
| 11 | gn gen out/asan --args='is_asan=true enable_nacl=false is_debug=false' |
| 12 | ninja -C out/asan base_unittests |
| 13 | tools/perry.py out/asan/base_unittests > perry.log & |
| 14 | tail -f perry.log |
| 15 | |
| 16 | You might want to run it in `screen` as it'll take a while. |
| 17 | """ |
| 18 | |
Raul Tambre | 66e754d | 2019-09-25 12:03:44 | [diff] [blame] | 19 | from __future__ import print_function |
| 20 | |
Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 21 | import argparse |
| 22 | import os |
| 23 | import multiprocessing |
| 24 | import subprocess |
| 25 | import sys |
| 26 | |
| 27 | |
| 28 | def _GetTestList(path_to_binary): |
| 29 | """Returns a set of full test names. |
| 30 | |
| 31 | Each test will be of the form "Case.Test". There will be a separate line |
| 32 | for each combination of Case/Test (there are often multiple tests in each |
| 33 | case). |
| 34 | """ |
| 35 | raw_output = subprocess.check_output([path_to_binary, "--gtest_list_tests"]) |
| 36 | input_lines = raw_output.splitlines() |
| 37 | |
| 38 | # The format of the gtest_list_tests output is: |
| 39 | # "Case1." |
| 40 | # " Test1 # <Optional extra stuff>" |
| 41 | # " Test2" |
| 42 | # "Case2." |
| 43 | # " Test1" |
| 44 | case_name = '' # Includes trailing dot. |
| 45 | test_set = set() |
| 46 | for line in input_lines: |
| 47 | if len(line) > 1: |
| 48 | if '#' in line: |
| 49 | line = line[:line.find('#')] |
| 50 | if line[0] == ' ': |
| 51 | # Indented means a test in previous case. |
| 52 | test_set.add(case_name + line.strip()) |
| 53 | else: |
| 54 | # New test case. |
| 55 | case_name = line.strip() |
| 56 | |
| 57 | return test_set |
| 58 | |
| 59 | |
| 60 | def _CheckForFailure(data): |
| 61 | test_binary, pair0, pair1 = data |
| 62 | p = subprocess.Popen( |
| 63 | [test_binary, '--gtest_repeat=5', '--gtest_shuffle', |
| 64 | '--gtest_filter=' + pair0 + ':' + pair1], |
| 65 | stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| 66 | out, _ = p.communicate() |
| 67 | if p.returncode != 0: |
| 68 | return (pair0, pair1, out) |
| 69 | return None |
| 70 | |
| 71 | |
| 72 | def _PrintStatus(i, total, failed): |
| 73 | status = '%d of %d tested (%d failures)' % (i+1, total, failed) |
Raul Tambre | 66e754d | 2019-09-25 12:03:44 | [diff] [blame] | 74 | print('\r%s%s' % (status, '\x1B[K'), end=' ') |
Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 75 | sys.stdout.flush() |
| 76 | |
| 77 | |
| 78 | def main(): |
| 79 | parser = argparse.ArgumentParser(description="Find failing pairs of tests.") |
| 80 | parser.add_argument('binary', help='Path to gtest binary or wrapper script.') |
| 81 | args = parser.parse_args() |
Raul Tambre | 66e754d | 2019-09-25 12:03:44 | [diff] [blame] | 82 | print('Getting test list...') |
Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 83 | all_tests = _GetTestList(args.binary) |
| 84 | permuted = [(args.binary, x, y) for x in all_tests for y in all_tests] |
| 85 | |
| 86 | failed = [] |
| 87 | pool = multiprocessing.Pool() |
| 88 | total_count = len(permuted) |
| 89 | for i, result in enumerate(pool.imap_unordered( |
| 90 | _CheckForFailure, permuted, 1)): |
| 91 | if result: |
Raul Tambre | 66e754d | 2019-09-25 12:03:44 | [diff] [blame] | 92 | print('\n--gtest_filter=%s:%s failed\n\n%s\n\n' % (result[0], result[1], |
| 93 | result[2])) |
Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 94 | failed.append(result) |
| 95 | _PrintStatus(i, total_count, len(failed)) |
| 96 | |
| 97 | pool.terminate() |
| 98 | pool.join() |
| 99 | |
| 100 | if failed: |
Raul Tambre | 66e754d | 2019-09-25 12:03:44 | [diff] [blame] | 101 | print('Failed pairs:') |
Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 102 | for f in failed: |
Raul Tambre | 66e754d | 2019-09-25 12:03:44 | [diff] [blame] | 103 | print(f[0], f[1]) |
Scott Graham | ab0c382 | 2017-11-16 19:08:55 | [diff] [blame] | 104 | |
| 105 | return 0 |
| 106 | |
| 107 | |
| 108 | if __name__ == '__main__': |
| 109 | sys.exit(main()) |