blob: a8228ae4c56f8e93316dad8bbf5fc4b2abe5aea5 [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import json
import optparse
import os
import shutil
import subprocess
import sys
import tempfile
import traceback
from slave import slave_utils
MISSING_SHARDS_MSG = r"""Missing results from the following shard(s): %s
It can happen in following cases:
* Test failed to start (missing *.dll/*.so dependency for example)
* Test crashed or hung
* Task expired because there are not enough bots available and are all used
* Swarming service experiences problems
Please examine logs to figure out what happened.
"""
def emit_warning(title, log=None):
print '@@@STEP_WARNINGS@@@'
print title
if log:
slave_utils.WriteLogLines(title, log.split('\n'))
def merge_shard_results(
output_dir, sancov_merger=None, coverage_dir=None):
"""Reads JSON test output from all shards and combines them into one.
Also merges sancov coverage data if coverage_dir is spefied.
Returns dict with merged test output on success or None on failure. Emits
annotations.
"""
# summary.json is produced by swarming.py itself. We are mostly interested
# in the number of shards.
try:
with open(os.path.join(output_dir, 'summary.json')) as f:
summary = json.load(f)
except (IOError, ValueError):
emit_warning(
'summary.json is missing or can not be read',
'Something is seriously wrong with swarming_client/ or the bot.')
return None
# Merge all JSON files together.
archs = []
modes = []
slowest_tests = []
results = []
missing_shards = []
for index, result in enumerate(summary['shards']):
if result is not None:
json_data = load_shard_json(output_dir, result['task_id'])
if json_data:
# On continuous bots, the test driver outputs exactly one item in the
# test results list for one architecture.
assert len(json_data) == 1
archs.append(json_data[0]['arch'])
modes.append(json_data[0]['mode'])
slowest_tests.extend(json_data[0]['slowest_tests'])
results.extend(json_data[0]['results'])
continue
missing_shards.append(index)
# If some shards are missing, make it known. Continue parsing anyway. Step
# should be red anyway, since swarming.py return non-zero exit code in that
# case.
if missing_shards:
as_str = ', '.join(map(str, missing_shards))
emit_warning(
'some shards did not complete: %s' % as_str,
MISSING_SHARDS_MSG % as_str)
# Not all tests run, combined JSON summary can not be trusted.
# TODO(machenbach): Implement using a tag in the test results that makes
# the step know they're incomplete.
# Handle the case when all shards fail. Return minimalistic dict that has all
# fields that a calling recipe expects to avoid recipe-level exceptions.
if len(missing_shards) == len(summary['shards']):
return [{'slowest_tests': [], 'results': []}]
# Each shard must have used the same arch and mode configuration.
assert len(set(archs)) == 1
assert len(set(modes)) == 1
# Merge coverage data if specified.
if coverage_dir:
for index, result in enumerate(summary['shards']):
exit_code = subprocess.call([
sys.executable, '-u', sancov_merger,
'--coverage-dir', coverage_dir,
'--swarming-output-dir', os.path.join(output_dir, result['task_id'])])
if exit_code:
emit_warning('error when merging coverage data of shard %d' % index)
return [{
'arch': archs[0],
'mode': modes[0],
'slowest_tests': sorted(
slowest_tests, key=lambda t: t['duration'], reverse=True)[:10],
'results': results,
}]
def load_shard_json(output_dir, task_id):
"""Reads JSON output of a single shard."""
# 'output.json' is set in v8/testing.py, V8SwarmingTest.
path = os.path.join(output_dir, task_id, 'output.json')
try:
with open(path) as f:
return json.load(f)
except (IOError, ValueError):
print >> sys.stderr, 'Missing or invalid v8 JSON file: %s' % path
return None
def main(args):
# Split |args| into options for shim and options for swarming.py script.
if '--' in args:
index = args.index('--')
shim_args, swarming_args = args[:index], args[index+1:]
else:
shim_args, swarming_args = args, []
# Parse shim's own options.
parser = optparse.OptionParser()
parser.add_option('--temp-root-dir', default=tempfile.gettempdir())
parser.add_option('--merged-test-output')
parser.add_option('--coverage-dir')
parser.add_option('--sancov-merger')
options, extra_args = parser.parse_args(shim_args)
# Validate options.
if extra_args:
parser.error('Unexpected command line arguments')
if options.coverage_dir and not options.sancov_merger:
parser.error('--sancov-merger is required for merging coverage data')
# Prepare a directory to store JSON files fetched from isolate.
task_output_dir = tempfile.mkdtemp(
suffix='_swarming', dir=options.temp_root_dir)
# Start building the command line for swarming.py.
args = [
'swarming',
]
args.extend(swarming_args)
args.extend([
'-output-dir', task_output_dir,
'-task-summary-json',
os.path.join(task_output_dir, 'summary.json'),
])
exit_code = 1
try:
# Run the real script, regardless of an exit code try to find and parse
# JSON output files, since exit code may indicate that the isolated task
# failed, not the swarming.py invocation itself.
exit_code = subprocess.call(args)
# Output parsing should not change exit code no matter what, so catch any
# exceptions and just log them.
try:
with open(options.merged_test_output, 'wb') as f:
json.dump(
merge_shard_results(
task_output_dir,
options.sancov_merger,
options.coverage_dir),
f, separators=(',', ':'))
except Exception:
emit_warning(
'failed to process v8 output JSON', traceback.format_exc())
finally:
shutil.rmtree(task_output_dir, ignore_errors=True)
return exit_code
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))