"""Contains two functions that run different test cases and the same test
case in parallel repeatedly to identify flaky tests.
"""
from __future__ import print_function
import os
import re
import subprocess
import time
FF_DATA_SUFFIX = '_flakies'
FF_SLEEP_INTERVAL = 10.0
FF_NUM_ITERATIONS = 100
FF_SUPERVISOR_ARGS = ['-r3', '--random-seed']
FF_OUTPUT_SUFFIX = '_purges'
FF_NUM_PROCS = 20
FF_NUM_REPEATS = 10
FF_TIMEOUT = 600
def FindShardingFlakiness(test_path, data_path, supervisor_args):
"""Finds flaky test cases by sharding and running a test for the specified
number of times. The data file is read at the beginning of each run to find
the last known counts and is overwritten at the end of each run with the new
counts. There is an optional sleep interval between each run so the script can
be killed without losing the data, useful for overnight (or weekend!) runs.
"""
failed_tests = {}
if os.path.exists(data_path):
data_file = open(data_path, 'r')
num_runs = int(data_file.readline().split(' ')[0])
num_passes = int(data_file.readline().split(' ')[0])
for line in data_file:
if line:
split_line = line.split(' -> ')
failed_tests[split_line[0]] = int(split_line[1])
data_file.close()
else:
num_runs = 0
num_passes = 0
log_lines = False
args = ['python', '../sharding_supervisor/sharding_supervisor.py']
args.extend(supervisor_args + [test_path])
proc = subprocess.Popen(args, stderr=subprocess.PIPE)
while True:
line = proc.stderr.readline()
if not line:
if proc.poll() is not None:
break
continue
print(line.rstrip())
if log_lines:
line = line.rstrip()
if line in failed_tests:
failed_tests[line] += 1
else:
failed_tests[line] = 1
elif line.find('FAILED TESTS:') >= 0:
log_lines = True
num_runs += 1
if proc.returncode == 0:
num_passes += 1
data_file = open(data_path, 'w')
print('%i runs' % num_runs)
data_file.write('%i runs\n' % num_runs)
print('%i passes' % num_passes)
data_file.write('%i passes\n' % num_passes)
for (test, count) in failed_tests.iteritems():
print('%s -> %i' % (test, count))
data_file.write('%s -> %i\n' % (test, count))
data_file.close()
def FindUnaryFlakiness(test_path, output_path, num_procs, num_repeats, timeout):
"""Runs all the test cases in a given test in parallel with itself, to get at
those that hold on to shared resources. The idea is that if a test uses a
unary resource, then running many instances of this test will purge out some
of them as failures or timeouts.
"""
test_name_regex = r'((\w+/)?\w+\.\w+(/\d+)?)'
test_start = re.compile('\[\s+RUN\s+\] ' + test_name_regex)
test_list = []
proc = subprocess.Popen([test_path], stdout=subprocess.PIPE)
while True:
line = proc.stdout.readline()
if not line:
if proc.poll() is not None:
break
continue
print(line.rstrip())
results = test_start.search(line)
if results:
test_list.append(results.group(1))
failures = []
index = 0
total = len(test_list)
for test_name in test_list:
num_fails = 0
num_terminated = 0
procs = []
args = [test_path, '--gtest_filter=' + test_name,
'--gtest_repeat=%i' % num_repeats]
while len(procs) < num_procs:
procs.append(subprocess.Popen(args))
seconds = 0
while procs:
for proc in procs:
if proc.poll() is not None:
if proc.returncode != 0:
++num_fails
procs.remove(proc)
if seconds > timeout:
num_fails += len(procs)
num_terminated = len(procs)
while procs:
procs.pop().terminate()
time.sleep(1.0)
seconds += 1
if num_fails:
line = '%s: %i failed' % (test_name, num_fails)
if num_terminated:
line += ' (%i terminated)' % num_terminated
failures.append(line)
print('%s (%i / %i): %i failed' % (test_name, index, total, num_fails))
index += 1
time.sleep(1.0)
print(failures)
data_file = open(output_path, 'w')
for line in failures:
data_file.write(line + '\n')
data_file.close()
def main():
if not args:
parser.error('You must specify a path to test!')
if not os.path.exists(args[0]):
parser.error('%s does not exist!' % args[0])
data_path = os.path.basename(args[0]) + FF_DATA_SUFFIX
output_path = os.path.basename(args[0]) + FF_OUTPUT_SUFFIX
for i in range(FF_NUM_ITERATIONS):
FindShardingFlakiness(args[0], data_path, FF_SUPERVISOR_ARGS)
print('That was just iteration %i of %i.' % (i + 1, FF_NUM_ITERATIONS))
time.sleep(FF_SLEEP_INTERVAL)
FindUnaryFlakiness(
args[0], output_path, FF_NUM_PROCS, FF_NUM_REPEATS, FF_TIMEOUT)
if __name__ == '__main__':
main()