| #!/usr/bin/env python |
| # Copyright 2017 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Runs all permutations of pairs of tests in a gtest binary to attempt to |
| detect state leakage between tests. |
| |
| Example invocation: |
| |
| gn gen out/asan --args='is_asan=true enable_nacl=false is_debug=false' |
| ninja -C out/asan base_unittests |
| tools/perry.py out/asan/base_unittests > perry.log & |
| tail -f perry.log |
| |
| You might want to run it in `screen` as it'll take a while. |
| """ |
| |
| from __future__import print_function |
| |
| import argparse |
| import os |
| import multiprocessing |
| import subprocess |
| import sys |
| |
| |
| def_GetTestList(path_to_binary): |
| """Returns a set of full test names. |
| |
| Each test will be of the form "Case.Test". There will be a separate line |
| for each combination of Case/Test (there are often multiple tests in each |
| case). |
| """ |
| raw_output= subprocess.check_output([path_to_binary,"--gtest_list_tests"]) |
| input_lines= raw_output.splitlines() |
| |
| # The format of the gtest_list_tests output is: |
| # "Case1." |
| # " Test1 # <Optional extra stuff>" |
| # " Test2" |
| # "Case2." |
| # " Test1" |
| case_name=''# Includes trailing dot. |
| test_set= set() |
| for linein input_lines: |
| if len(line)>1: |
| if'#'in line: |
| line= line[:line.find('#')] |
| if line[0]==' ': |
| # Indented means a test in previous case. |
| test_set.add(case_name+ line.strip()) |
| else: |
| # New test case. |
| case_name= line.strip() |
| |
| return test_set |
| |
| |
| def_CheckForFailure(data): |
| test_binary, pair0, pair1= data |
| p= subprocess.Popen( |
| [test_binary,'--gtest_repeat=5','--gtest_shuffle', |
| '--gtest_filter='+ pair0+':'+ pair1], |
| stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
| out, _= p.communicate() |
| if p.returncode!=0: |
| return(pair0, pair1, out) |
| returnNone |
| |
| |
| def_PrintStatus(i, total, failed): |
| status='%d of %d tested (%d failures)'%(i+1, total, failed) |
| print('\r%s%s'%(status,'\x1B[K'), end=' ') |
| sys.stdout.flush() |
| |
| |
| def main(): |
| parser= argparse.ArgumentParser(description="Find failing pairs of tests.") |
| parser.add_argument('binary', help='Path to gtest binary or wrapper script.') |
| args= parser.parse_args() |
| print('Getting test list...') |
| all_tests=_GetTestList(args.binary) |
| permuted=[(args.binary, x, y)for xin all_testsfor yin all_tests] |
| |
| failed=[] |
| pool= multiprocessing.Pool() |
| total_count= len(permuted) |
| for i, resultin enumerate(pool.imap_unordered( |
| _CheckForFailure, permuted,1)): |
| if result: |
| print('\n--gtest_filter=%s:%s failed\n\n%s\n\n'%(result[0], result[1], |
| result[2])) |
| failed.append(result) |
| _PrintStatus(i, total_count, len(failed)) |
| |
| pool.terminate() |
| pool.join() |
| |
| if failed: |
| print('Failed pairs:') |
| for fin failed: |
| print(f[0], f[1]) |
| |
| return0 |
| |
| |
| if __name__=='__main__': |
| sys.exit(main()) |