blob: 4a0ed0070c39853c8024c8eb85dfa31e3d9d7f24 [file] [log] [blame]
Scott Grahamab0c3822017-11-16 19:08:551#!/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
7detect state leakage between tests.
8
9Example invocation:
10
11gn gen out/asan --args='is_asan=true enable_nacl=false is_debug=false'
12ninja -C out/asan base_unittests
13tools/perry.py out/asan/base_unittests > perry.log &
14tail -f perry.log
15
16You might want to run it in `screen` as it'll take a while.
17"""
18
Raul Tambre66e754d2019-09-25 12:03:4419from __future__ import print_function
20
Scott Grahamab0c3822017-11-16 19:08:5521import argparse
22import os
23import multiprocessing
24import subprocess
25import sys
26
27
28def _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
60def _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
72def _PrintStatus(i, total, failed):
73 status = '%d of %d tested (%d failures)' % (i+1, total, failed)
Raul Tambre66e754d2019-09-25 12:03:4474 print('\r%s%s' % (status, '\x1B[K'), end=' ')
Scott Grahamab0c3822017-11-16 19:08:5575 sys.stdout.flush()
76
77
78def 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 Tambre66e754d2019-09-25 12:03:4482 print('Getting test list...')
Scott Grahamab0c3822017-11-16 19:08:5583 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 Tambre66e754d2019-09-25 12:03:4492 print('\n--gtest_filter=%s:%s failed\n\n%s\n\n' % (result[0], result[1],
93 result[2]))
Scott Grahamab0c3822017-11-16 19:08:5594 failed.append(result)
95 _PrintStatus(i, total_count, len(failed))
96
97 pool.terminate()
98 pool.join()
99
100 if failed:
Raul Tambre66e754d2019-09-25 12:03:44101 print('Failed pairs:')
Scott Grahamab0c3822017-11-16 19:08:55102 for f in failed:
Raul Tambre66e754d2019-09-25 12:03:44103 print(f[0], f[1])
Scott Grahamab0c3822017-11-16 19:08:55104
105 return 0
106
107
108if __name__ == '__main__':
109 sys.exit(main())