blob: a67cd6ca44ad870fb856fa5834ebd70f473cc4de [file] [log] [blame]
#!/usr/bin/env python2
"""Generate summary report for ChromeOS toolchain waterfalls."""
# Desired future features (to be added):
# - arguments to allow generating only the main waterfall report,
# or only the rotating builder reports, or only the failures
# report; or the waterfall reports without the failures report.
# - Better way of figuring out which dates/builds to generate
# reports for: probably an argument specifying a date or a date
# range, then use something like the new buildbot utils to
# query the build logs to find the right build numbers for the
# builders for the specified dates.
# - Store/get the json/data files in mobiletc-prebuild's x20 area.
# - Update data in json file to reflect, for each testsuite, which
# tests are not expected to run on which boards; update this
# script to use that data appropriately.
# - Make sure user's prodaccess is up-to-date before trying to use
# this script.
# - Add some nice formatting/highlighting to reports.
from __future__ import print_function
import argparse
import getpass
import json
import os
import re
import shutil
import sys
import time
from cros_utils import command_executer
# All the test suites whose data we might want for the reports.
TESTS = (('bvt-inline', 'HWTest'), ('bvt-cq', 'HWTest'), ('security', 'HWTest'),
('kernel_daily_regression', 'HWTest'), ('kernel_daily_benchmarks',
'HWTest'),)
# The main waterfall builders, IN THE ORDER IN WHICH WE WANT THEM
# LISTED IN THE REPORT.
WATERFALL_BUILDERS = [
'amd64-llvm-next-toolchain',
'arm-llvm-next-toolchain',
'arm64-llvm-next-toolchain',
]
DATA_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-report-data/'
ARCHIVE_DIR = '/google/data/rw/users/mo/mobiletc-prebuild/waterfall-reports/'
DOWNLOAD_DIR = '/tmp/waterfall-logs'
MAX_SAVE_RECORDS = 7
BUILD_DATA_FILE = '%s/build-data.txt' % DATA_DIR
GCC_ROTATING_BUILDER = 'gcc_toolchain'
LLVM_ROTATING_BUILDER = 'llvm_next_toolchain'
ROTATING_BUILDERS = [GCC_ROTATING_BUILDER, LLVM_ROTATING_BUILDER]
# For int-to-string date conversion. Note, the index of the month in this
# list needs to correspond to the month's integer value. i.e. 'Sep' must
# be as MONTHS[9].
MONTHS = [
'', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct',
'Nov', 'Dec'
]
def format_date(int_date):
"""Convert an integer date to a string date. YYYYMMDD -> YYYY-MMM-DD"""
if int_date == 0:
return 'today'
tmp_date = int_date
day = tmp_date % 100
tmp_date = tmp_date / 100
month = tmp_date % 100
year = tmp_date / 100
month_str = MONTHS[month]
date_str = '%d-%s-%d' % (year, month_str, day)
return date_str
def EmailReport(report_file, report_type, date, email_to):
subject = '%s Waterfall Summary report, %s' % (report_type, date)
sendgmr_path = '/google/data/ro/projects/gws-sre/sendgmr'
command = ('%s --to=%s --subject="%s" --body_file=%s' %
(sendgmr_path, email_to, subject, report_file))
command_executer.GetCommandExecuter().RunCommand(command)
def PruneOldFailures(failure_dict, int_date):
earliest_date = int_date - MAX_SAVE_RECORDS
for suite in failure_dict:
suite_dict = failure_dict[suite]
test_keys_to_remove = []
for test in suite_dict:
test_dict = suite_dict[test]
msg_keys_to_remove = []
for msg in test_dict:
fails = test_dict[msg]
i = 0
while i < len(fails) and fails[i][0] <= earliest_date:
i += 1
new_fails = fails[i:]
test_dict[msg] = new_fails
if len(new_fails) == 0:
msg_keys_to_remove.append(msg)
for k in msg_keys_to_remove:
del test_dict[k]
suite_dict[test] = test_dict
if len(test_dict) == 0:
test_keys_to_remove.append(test)
for k in test_keys_to_remove:
del suite_dict[k]
failure_dict[suite] = suite_dict
def GetBuildID(build_bot, date):
"""Get the build id for a build_bot at a given date."""
day = '{day:02d}'.format(day=date % 100)
mon = MONTHS[date / 100 % 100]
date_string = mon + ' ' + day
if build_bot in WATERFALL_BUILDERS:
url = 'https://uberchromegw.corp.google.com/i/chromeos/' + \
'builders/%s?numbuilds=200' % build_bot
if build_bot in ROTATING_BUILDERS:
url = 'https://uberchromegw.corp.google.com/i/chromiumos.tryserver/' + \
'builders/%s?numbuilds=200' % build_bot
command = 'sso_client %s' % url
retval = 1
retry_time = 3
while retval and retry_time:
retval, output, _ = \
command_executer.GetCommandExecuter().RunCommandWOutput(command, \
print_to_console=False)
retry_time -= 1
if retval:
return []
out = output.split('\n')
line_num = 0
build_id = []
# Parse the output like this
# <td>Dec 14 10:55</td>
# <td class="revision">??</td>
# <td failure</td><td><a href="../builders/gcc_toolchain/builds/109">#109</a>
while line_num < len(out):
if date_string in out[line_num]:
if line_num + 2 < len(out):
build_num_line = out[line_num + 2]
raw_num = re.findall(r'builds/\d+', build_num_line)
# raw_num is ['builds/109'] in the example.
if raw_num:
build_id.append(int(raw_num[0].split('/')[1]))
line_num += 1
return build_id
def GenerateFailuresReport(fail_dict, date):
filename = 'waterfall_report.failures.%s.txt' % date
date_string = format_date(date)
with open(filename, 'w') as out_file:
# Write failure report section.
out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string)
# We want to sort the errors and output them in order of the ones that occur
# most often. So we have to collect the data about all of them, then sort
# it.
error_groups = []
for suite in fail_dict:
suite_dict = fail_dict[suite]
if suite_dict:
for test in suite_dict:
test_dict = suite_dict[test]
for err_msg in test_dict:
err_list = test_dict[err_msg]
sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True)
err_group = [len(sorted_list), suite, test, err_msg, sorted_list]
error_groups.append(err_group)
# Sort the errors by the number of errors of each type. Then output them in
# order.
sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True)
for i in range(0, len(sorted_errors)):
err_group = sorted_errors[i]
suite = err_group[1]
test = err_group[2]
err_msg = err_group[3]
err_list = err_group[4]
out_file.write('Suite: %s\n' % suite)
out_file.write(' %s (%d failures)\n' % (test, len(err_list)))
out_file.write(' (%s)\n' % err_msg)
for i in range(0, len(err_list)):
err = err_list[i]
out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1],
err[2]))
out_file.write('\n')
print('Report generated in %s.' % filename)
return filename
def GenerateWaterfallReport(report_dict, fail_dict, waterfall_type, date,
omit_failures):
"""Write out the actual formatted report."""
filename = 'waterfall_report.%s_waterfall.%s.txt' % (waterfall_type, date)
date_string = ''
date_list = report_dict['date']
num_dates = len(date_list)
i = 0
for d in date_list:
date_string += d
if i < num_dates - 1:
date_string += ', '
i += 1
if waterfall_type == 'main':
report_list = WATERFALL_BUILDERS
else:
report_list = report_dict.keys()
with open(filename, 'w') as out_file:
# Write Report Header
out_file.write('\nStatus of %s Waterfall Builds from %s\n\n' %
(waterfall_type, date_string))
out_file.write(' '
' kernel kernel\n')
out_file.write(' Build bvt- bvt-cq '
' security daily daily\n')
out_file.write(' status inline '
' regression benchmarks\n')
out_file.write(' [P/ F/ DR]* [P/ F /DR]* '
'[P/ F/ DR]* [P/ F/ DR]* [P/ F/ DR]*\n\n')
# Write daily waterfall status section.
for i in range(0, len(report_list)):
builder = report_list[i]
if builder == 'date':
continue
if builder not in report_dict:
out_file.write('Unable to find information for %s.\n\n' % builder)
continue
build_dict = report_dict[builder]
status = build_dict.get('build_status', 'bad')
inline = build_dict.get('bvt-inline', '[??/ ?? /??]')
cq = build_dict.get('bvt-cq', '[??/ ?? /??]')
inline_color = build_dict.get('bvt-inline-color', '')
cq_color = build_dict.get('bvt-cq-color', '')
if 'x86' not in builder:
security = build_dict.get('security', '[??/ ?? /??]')
security_color = build_dict.get('security-color', '')
if 'gcc' in builder:
regression = build_dict.get('kernel_daily_regression', '[??/ ?? /??]')
bench = build_dict.get('kernel_daily_benchmarks', '[??/ ?? /??]')
regression_color = build_dict.get('kernel_daily_regression-color', '')
bench_color = build_dict.get('kernel_daily_benchmarks-color', '')
out_file.write(' %6s %6s'
' %6s %6s %6s\n' %
(inline_color, cq_color, security_color,
regression_color, bench_color))
out_file.write('%25s %3s %s %s %s %s %s\n' %
(builder, status, inline, cq, security, regression,
bench))
else:
out_file.write(' %6s %6s'
' %6s\n' % (inline_color, cq_color,
security_color))
out_file.write('%25s %3s %s %s %s\n' % (builder, status, inline, cq,
security))
else:
out_file.write(' %6s %6s\n' %
(inline_color, cq_color))
out_file.write('%25s %3s %s %s\n' % (builder, status, inline, cq))
if 'build_link' in build_dict:
out_file.write('%s\n\n' % build_dict['build_link'])
out_file.write('\n\n*P = Number of tests in suite that Passed; F = '
'Number of tests in suite that Failed; DR = Number of tests'
' in suite that Didn\'t Run.\n')
if omit_failures:
print('Report generated in %s.' % filename)
return filename
# Write failure report section.
out_file.write('\n\nSummary of Test Failures as of %s\n\n' % date_string)
# We want to sort the errors and output them in order of the ones that occur
# most often. So we have to collect the data about all of them, then sort
# it.
error_groups = []
for suite in fail_dict:
suite_dict = fail_dict[suite]
if suite_dict:
for test in suite_dict:
test_dict = suite_dict[test]
for err_msg in test_dict:
err_list = test_dict[err_msg]
sorted_list = sorted(err_list, key=lambda x: x[0], reverse=True)
err_group = [len(sorted_list), suite, test, err_msg, sorted_list]
error_groups.append(err_group)
# Sort the errors by the number of errors of each type. Then output them in
# order.
sorted_errors = sorted(error_groups, key=lambda x: x[0], reverse=True)
for i in range(0, len(sorted_errors)):
err_group = sorted_errors[i]
suite = err_group[1]
test = err_group[2]
err_msg = err_group[3]
err_list = err_group[4]
out_file.write('Suite: %s\n' % suite)
out_file.write(' %s (%d failures)\n' % (test, len(err_list)))
out_file.write(' (%s)\n' % err_msg)
for i in range(0, len(err_list)):
err = err_list[i]
out_file.write(' %s, %s, %s\n' % (format_date(err[0]), err[1],
err[2]))
out_file.write('\n')
print('Report generated in %s.' % filename)
return filename
def UpdateReport(report_dict, builder, test, report_date, build_link,
test_summary, board, color):
"""Update the data in our report dictionary with current test's data."""
if 'date' not in report_dict:
report_dict['date'] = [report_date]
elif report_date not in report_dict['date']:
# It is possible that some of the builders started/finished on different
# days, so we allow for multiple dates in the reports.
report_dict['date'].append(report_date)
build_key = ''
if builder == GCC_ROTATING_BUILDER:
build_key = '%s-gcc-toolchain' % board
elif builder == LLVM_ROTATING_BUILDER:
build_key = '%s-llvm-next-toolchain' % board
else:
build_key = builder
if build_key not in report_dict.keys():
build_dict = dict()
else:
build_dict = report_dict[build_key]
if 'build_link' not in build_dict:
build_dict['build_link'] = build_link
if 'date' not in build_dict:
build_dict['date'] = report_date
if 'board' in build_dict and build_dict['board'] != board:
raise RuntimeError(
'Error: Two different boards (%s,%s) in one build (%s)!' %
(board, build_dict['board'], build_link))
build_dict['board'] = board
color_key = '%s-color' % test
build_dict[color_key] = color
# Check to see if we already have a build status for this build_key
status = ''
if 'build_status' in build_dict.keys():
# Use current build_status, unless current test failed (see below).
status = build_dict['build_status']
if not test_summary:
# Current test data was not available, so something was bad with build.
build_dict['build_status'] = 'bad'
build_dict[test] = '[ no data ]'
else:
build_dict[test] = test_summary
if not status:
# Current test ok; no other data, so assume build was ok.
build_dict['build_status'] = 'ok'
report_dict[build_key] = build_dict
def UpdateBuilds(builds):
"""Update the data in our build-data.txt file."""
# The build data file records the last build number for which we
# generated a report. When we generate the next report, we read
# this data and increment it to get the new data; when we finish
# generating the reports, we write the updated values into this file.
# NOTE: One side effect of doing this at the end: If the script
# fails in the middle of generating a report, this data does not get
# updated.
with open(BUILD_DATA_FILE, 'w') as fp:
gcc_max = 0
llvm_max = 0
for b in builds:
if b[0] == GCC_ROTATING_BUILDER:
gcc_max = max(gcc_max, b[1])
elif b[0] == LLVM_ROTATING_BUILDER:
llvm_max = max(llvm_max, b[1])
else:
fp.write('%s,%d\n' % (b[0], b[1]))
if gcc_max > 0:
fp.write('%s,%d\n' % (GCC_ROTATING_BUILDER, gcc_max))
if llvm_max > 0:
fp.write('%s,%d\n' % (LLVM_ROTATING_BUILDER, llvm_max))
def GetBuilds(date=0):
"""Get build id from builds."""
# If date is set, get the build id from waterfall.
builds = []
if date:
for builder in WATERFALL_BUILDERS + ROTATING_BUILDERS:
build_ids = GetBuildID(builder, date)
for build_id in build_ids:
builds.append((builder, build_id))
return builds
# If date is not set, we try to get the most recent builds.
# Read the values of the last builds used to generate a report, and
# increment them appropriately, to get values for generating the
# current report. (See comments in UpdateBuilds).
with open(BUILD_DATA_FILE, 'r') as fp:
lines = fp.readlines()
for l in lines:
l = l.rstrip()
words = l.split(',')
builder = words[0]
build = int(words[1])
builds.append((builder, build + 1))
# NOTE: We are assuming here that there are always 2 daily builds in
# each of the rotating builders. I am not convinced this is a valid
# assumption.
if builder in ROTATING_BUILDERS:
builds.append((builder, build + 2))
return builds
def RecordFailures(failure_dict, platform, suite, builder, int_date, log_file,
build_num, failed):
"""Read and update the stored data about test failures."""
# Get the dictionary for this particular test suite from the failures
# dictionary.
suite_dict = failure_dict[suite]
# Read in the entire log file for this test/build.
with open(log_file, 'r') as in_file:
lines = in_file.readlines()
# Update the entries in the failure dictionary for each test within this suite
# that failed.
for test in failed:
# Check to see if there is already an entry in the suite dictionary for this
# test; if so use that, otherwise create a new entry.
if test in suite_dict:
test_dict = suite_dict[test]
else:
test_dict = dict()
# Parse the lines from the log file, looking for lines that indicate this
# test failed.
msg = ''
for l in lines:
words = l.split()
if len(words) < 3:
continue
if ((words[0] == test and words[1] == 'ERROR:') or
(words[0] == 'provision' and words[1] == 'FAIL:')):
words = words[2:]
# Get the error message for the failure.
msg = ' '.join(words)
if not msg:
msg = 'Unknown_Error'
# Look for an existing entry for this error message in the test dictionary.
# If found use that, otherwise create a new entry for this error message.
if msg in test_dict:
error_list = test_dict[msg]
else:
error_list = list()
# Create an entry for this new failure
new_item = [int_date, platform, builder, build_num]
# Add this failure to the error list if it's not already there.
if new_item not in error_list:
error_list.append([int_date, platform, builder, build_num])
# Sort the error list by date.
error_list.sort(key=lambda x: x[0])
# Calculate the earliest date to save; delete records for older failures.
earliest_date = int_date - MAX_SAVE_RECORDS
i = 0
while i < len(error_list) and error_list[i][0] <= earliest_date:
i += 1
if i > 0:
error_list = error_list[i:]
# Save the error list in the test's dictionary, keyed on error_msg.
test_dict[msg] = error_list
# Save the updated test dictionary in the test_suite dictionary.
suite_dict[test] = test_dict
# Save the updated test_suite dictionary in the failure dictionary.
failure_dict[suite] = suite_dict
def ParseLogFile(log_file, test_data_dict, failure_dict, test, builder,
build_num, build_link):
"""Parse the log file from the given builder, build_num and test.
Also adds the results for this test to our test results dictionary,
and calls RecordFailures, to update our test failure data.
"""
print('Parsing file %s' % log_file)
lines = []
with open(log_file, 'r') as infile:
lines = infile.readlines()
passed = {}
failed = {}
not_run = {}
date = ''
status = ''
board = ''
num_provision_errors = 0
build_ok = True
afe_line = ''
for line in lines:
if line.rstrip() == '<title>404 Not Found</title>':
print('Warning: File for %s (build number %d), %s was not found.' %
(builder, build_num, test))
build_ok = False
break
if '[ PASSED ]' in line:
test_name = line.split()[0]
if test_name != 'Suite':
passed[test_name] = True
elif '[ FAILED ]' in line:
test_name = line.split()[0]
if test_name == 'provision':
num_provision_errors += 1
not_run[test_name] = True
elif test_name != 'Suite':
failed[test_name] = True
elif line.startswith('started: '):
date = line.rstrip()
date = date[9:]
date_obj = time.strptime(date, '%a %b %d %H:%M:%S %Y')
int_date = (
date_obj.tm_year * 10000 + date_obj.tm_mon * 100 + date_obj.tm_mday)
date = time.strftime('%a %b %d %Y', date_obj)
elif not status and line.startswith('status: '):
status = line.rstrip()
words = status.split(':')
status = words[-1]
elif line.find('Suite passed with a warning') != -1:
status = 'WARNING'
elif line.startswith('@@@STEP_LINK@Link to suite@'):
afe_line = line.rstrip()
words = afe_line.split('@')
for w in words:
if w.startswith('http'):
afe_line = w
afe_line = afe_line.replace('&amp;', '&')
elif 'INFO: RunCommand:' in line:
words = line.split()
for i in range(0, len(words) - 1):
if words[i] == '--board':
board = words[i + 1]
test_dict = test_data_dict[test]
test_list = test_dict['tests']
if build_ok:
for t in test_list:
if not t in passed and not t in failed:
not_run[t] = True
total_pass = len(passed)
total_fail = len(failed)
total_notrun = len(not_run)
else:
total_pass = 0
total_fail = 0
total_notrun = 0
status = 'Not found.'
if not build_ok:
return [], date, board, 0, ' '
build_dict = dict()
build_dict['id'] = build_num
build_dict['builder'] = builder
build_dict['date'] = date
build_dict['build_link'] = build_link
build_dict['total_pass'] = total_pass
build_dict['total_fail'] = total_fail
build_dict['total_not_run'] = total_notrun
build_dict['afe_job_link'] = afe_line
build_dict['provision_errors'] = num_provision_errors
if status.strip() == 'SUCCESS':
build_dict['color'] = 'green '
elif status.strip() == 'FAILURE':
build_dict['color'] = ' red '
elif status.strip() == 'WARNING':
build_dict['color'] = 'orange'
else:
build_dict['color'] = ' '
# Use YYYYMMDD (integer) as the build record key
if build_ok:
if board in test_dict:
board_dict = test_dict[board]
else:
board_dict = dict()
board_dict[int_date] = build_dict
# Only keep the last 5 records (based on date)
keys_list = board_dict.keys()
if len(keys_list) > MAX_SAVE_RECORDS:
min_key = min(keys_list)
del board_dict[min_key]
# Make sure changes get back into the main dictionary
test_dict[board] = board_dict
test_data_dict[test] = test_dict
if len(failed) > 0:
RecordFailures(failure_dict, board, test, builder, int_date, log_file,
build_num, failed)
summary_result = '[%2d/ %2d/ %2d]' % (total_pass, total_fail, total_notrun)
return summary_result, date, board, int_date, build_dict['color']
def DownloadLogFile(builder, buildnum, test, test_family):
ce = command_executer.GetCommandExecuter()
os.system('mkdir -p %s/%s/%s' % (DOWNLOAD_DIR, builder, test))
if builder in ROTATING_BUILDERS:
source = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver'
'/builders/%s/builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' %
(builder, buildnum, test_family, test))
build_link = ('https://uberchromegw.corp.google.com/i/chromiumos.tryserver'
'/builders/%s/builds/%d' % (builder, buildnum))
else:
source = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s/'
'builds/%d/steps/%s%%20%%5B%s%%5D/logs/stdio' %
(builder, buildnum, test_family, test))
build_link = ('https://uberchromegw.corp.google.com/i/chromeos/builders/%s'
'/builds/%d' % (builder, buildnum))
target = '%s/%s/%s/%d' % (DOWNLOAD_DIR, builder, test, buildnum)
if not os.path.isfile(target) or os.path.getsize(target) == 0:
cmd = 'sso_client %s > %s' % (source, target)
status = ce.RunCommand(cmd)
if status != 0:
return '', ''
return target, build_link
# Check for prodaccess.
def CheckProdAccess():
status, output, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
'prodcertstatus')
if status != 0:
return False
# Verify that status is not expired
if 'expires' in output:
return True
return False
def ValidOptions(parser, options):
too_many_options = False
if options.main:
if options.rotating or options.failures_report:
too_many_options = True
elif options.rotating and options.failures_report:
too_many_options = True
if too_many_options:
parser.error('Can only specify one of --main, --rotating or'
' --failures_report.')
conflicting_failure_options = False
if options.failures_report and options.omit_failures:
conflicting_failure_options = True
parser.error('Cannot specify both --failures_report and --omit_failures.')
email_ok = True
if options.email and options.email.find('@') == -1:
email_ok = False
parser.error('"%s" is not a valid email address; it must contain "@..."' %
options.email)
return not too_many_options and not conflicting_failure_options and email_ok
def Main(argv):
"""Main function for this script."""
parser = argparse.ArgumentParser()
parser.add_argument(
'--main',
dest='main',
default=False,
action='store_true',
help='Generate report only for main waterfall '
'builders.')
parser.add_argument(
'--rotating',
dest='rotating',
default=False,
action='store_true',
help='Generate report only for rotating builders.')
parser.add_argument(
'--failures_report',
dest='failures_report',
default=False,
action='store_true',
help='Only generate the failures section of the report.')
parser.add_argument(
'--omit_failures',
dest='omit_failures',
default=False,
action='store_true',
help='Do not generate the failures section of the report.')
parser.add_argument(
'--no_update',
dest='no_update',
default=False,
action='store_true',
help='Run reports, but do not update the data files.')
parser.add_argument(
'--date',
dest='date',
default=0,
type=int,
help='The date YYYYMMDD of waterfall report.')
parser.add_argument(
'--email',
dest='email',
default='',
help='Email address to use for sending the report.')
options = parser.parse_args(argv)
if not ValidOptions(parser, options):
return 1
main_only = options.main
rotating_only = options.rotating
failures_report = options.failures_report
omit_failures = options.omit_failures
date = options.date
test_data_dict = dict()
failure_dict = dict()
prod_access = CheckProdAccess()
if not prod_access:
print('ERROR: Please run prodaccess first.')
return
with open('%s/waterfall-test-data.json' % DATA_DIR, 'r') as input_file:
test_data_dict = json.load(input_file)
with open('%s/test-failure-data.json' % DATA_DIR, 'r') as fp:
failure_dict = json.load(fp)
builds = GetBuilds(date)
waterfall_report_dict = dict()
rotating_report_dict = dict()
int_date = 0
for test_desc in TESTS:
test, test_family = test_desc
for build in builds:
(builder, buildnum) = build
if test.startswith('kernel') and 'llvm' in builder:
continue
if 'x86' in builder and not test.startswith('bvt'):
continue
target, build_link = DownloadLogFile(builder, buildnum, test, test_family)
if os.path.exists(target):
test_summary, report_date, board, tmp_date, color = ParseLogFile(
target, test_data_dict, failure_dict, test, builder, buildnum,
build_link)
if not test_summary:
continue
if tmp_date != 0:
int_date = tmp_date
if builder in ROTATING_BUILDERS:
UpdateReport(rotating_report_dict, builder, test, report_date,
build_link, test_summary, board, color)
else:
UpdateReport(waterfall_report_dict, builder, test, report_date,
build_link, test_summary, board, color)
PruneOldFailures(failure_dict, int_date)
if options.email:
email_to = options.email
else:
email_to = getpass.getuser()
if waterfall_report_dict and not rotating_only and not failures_report:
main_report = GenerateWaterfallReport(waterfall_report_dict, failure_dict,
'main', int_date, omit_failures)
EmailReport(main_report, 'Main', format_date(int_date), email_to)
shutil.copy(main_report, ARCHIVE_DIR)
if rotating_report_dict and not main_only and not failures_report:
rotating_report = GenerateWaterfallReport(
rotating_report_dict, failure_dict, 'rotating', int_date, omit_failures)
EmailReport(rotating_report, 'Rotating', format_date(int_date), email_to)
shutil.copy(rotating_report, ARCHIVE_DIR)
if failures_report:
failures_report = GenerateFailuresReport(failure_dict, int_date)
EmailReport(failures_report, 'Failures', format_date(int_date), email_to)
shutil.copy(failures_report, ARCHIVE_DIR)
if not options.no_update:
with open('%s/waterfall-test-data.json' % DATA_DIR, 'w') as out_file:
json.dump(test_data_dict, out_file, indent=2)
with open('%s/test-failure-data.json' % DATA_DIR, 'w') as out_file:
json.dump(failure_dict, out_file, indent=2)
UpdateBuilds(builds)
if __name__ == '__main__':
Main(sys.argv[1:])
sys.exit(0)