160 lines
5.7 KiB
Python
160 lines
5.7 KiB
Python
|
#!/usr/bin/env python
|
||
|
# Copyright 2017 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 argparse
|
||
|
import json
|
||
|
import logging
|
||
|
import os
|
||
|
import subprocess
|
||
|
import sys
|
||
|
|
||
|
|
||
|
def collect_task(
|
||
|
collect_cmd, merge_script, build_properties, merge_arguments,
|
||
|
task_output_dir, output_json):
|
||
|
"""Collect and merge the results of a task.
|
||
|
|
||
|
This is a relatively thin wrapper script around a `swarming.py collect`
|
||
|
command and a subsequent results merge to ensure that the recipe system
|
||
|
treats them as a single step. The results merge can either be the default
|
||
|
one provided by results_merger or a python script provided as merge_script.
|
||
|
|
||
|
Args:
|
||
|
collect_cmd: The `swarming.py collect` command to run. Should not contain
|
||
|
a --task-output-dir argument.
|
||
|
merge_script: A merge/postprocessing script that should be run to
|
||
|
merge the results. This script will be invoked as
|
||
|
|
||
|
<merge_script> \
|
||
|
[--build-properties <string JSON>] \
|
||
|
[merge arguments...] \
|
||
|
--summary-json <summary json> \
|
||
|
-o <merged json path> \
|
||
|
<shard json>...
|
||
|
|
||
|
where the merge arguments are the contents of merge_arguments_json.
|
||
|
build_properties: A string containing build information to
|
||
|
pass to the merge script in JSON form.
|
||
|
merge_arguments: A string containing additional arguments to pass to
|
||
|
the merge script in JSON form.
|
||
|
task_output_dir: A path to a directory in which swarming will write the
|
||
|
output of the task, including a summary JSON and all of the individual
|
||
|
shard results.
|
||
|
output_json: A path to a JSON file to which the merged results should be
|
||
|
written. The merged results should be in the JSON Results File Format
|
||
|
(https://www.chromium.org/developers/the-json-test-results-format)
|
||
|
and may optionally contain a top level "links" field that may contain a
|
||
|
dict mapping link text to URLs, for a set of links that will be included
|
||
|
in the buildbot output.
|
||
|
Returns:
|
||
|
The exit code of collect_cmd or merge_cmd.
|
||
|
"""
|
||
|
logging.debug('Using task_output_dir: %r', task_output_dir)
|
||
|
if os.path.exists(task_output_dir):
|
||
|
logging.warn('task_output_dir %r already exists!', task_output_dir)
|
||
|
existing_contents = []
|
||
|
try:
|
||
|
for p in os.listdir(task_output_dir):
|
||
|
existing_contents.append(os.path.join(task_output_dir, p))
|
||
|
except (OSError, IOError) as e:
|
||
|
logging.error('Error while examining existing task_output_dir: %s', e)
|
||
|
|
||
|
logging.warn('task_output_dir existing content: %r', existing_contents)
|
||
|
|
||
|
collect_cmd.extend(['--task-output-dir', task_output_dir])
|
||
|
|
||
|
logging.info('collect_cmd: %s', ' '.join(collect_cmd))
|
||
|
collect_result = subprocess.call(collect_cmd)
|
||
|
if collect_result != 0:
|
||
|
logging.warn('collect_cmd had non-zero return code: %s', collect_result)
|
||
|
|
||
|
task_output_dir_contents = []
|
||
|
try:
|
||
|
task_output_dir_contents.extend(
|
||
|
os.path.join(task_output_dir, p)
|
||
|
for p in os.listdir(task_output_dir))
|
||
|
except (OSError, IOError) as e:
|
||
|
logging.error('Error while processing task_output_dir: %s', e)
|
||
|
|
||
|
logging.debug('Contents of task_output_dir: %r', task_output_dir_contents)
|
||
|
if not task_output_dir_contents:
|
||
|
logging.warn(
|
||
|
'No files found in task_output_dir: %r',
|
||
|
task_output_dir)
|
||
|
|
||
|
task_output_subdirs = (
|
||
|
p for p in task_output_dir_contents
|
||
|
if os.path.isdir(p))
|
||
|
shard_json_files = [
|
||
|
os.path.join(subdir, 'output.json')
|
||
|
for subdir in task_output_subdirs]
|
||
|
extant_shard_json_files = [
|
||
|
f for f in shard_json_files if os.path.exists(f)]
|
||
|
|
||
|
if shard_json_files != extant_shard_json_files:
|
||
|
logging.warn(
|
||
|
'Expected output.json file missing: %r\nFound: %r\nExpected: %r\n',
|
||
|
set(shard_json_files) - set(extant_shard_json_files),
|
||
|
extant_shard_json_files,
|
||
|
shard_json_files)
|
||
|
|
||
|
if not extant_shard_json_files:
|
||
|
logging.warn(
|
||
|
'No shard json files found in task_output_dir: %r\nFound %r',
|
||
|
task_output_dir, task_output_dir_contents)
|
||
|
|
||
|
logging.debug('Found shard_json_files: %r', shard_json_files)
|
||
|
|
||
|
summary_json_file = os.path.join(task_output_dir, 'summary.json')
|
||
|
|
||
|
merge_result = 0
|
||
|
|
||
|
merge_cmd = [sys.executable, merge_script]
|
||
|
if build_properties:
|
||
|
merge_cmd.extend(('--build-properties', build_properties))
|
||
|
if os.path.exists(summary_json_file):
|
||
|
merge_cmd.extend(('--summary-json', summary_json_file))
|
||
|
else:
|
||
|
logging.warn('Summary json file missing: %r', summary_json_file)
|
||
|
if merge_arguments:
|
||
|
merge_cmd.extend(json.loads(merge_arguments))
|
||
|
merge_cmd.extend(('-o', output_json))
|
||
|
merge_cmd.extend(extant_shard_json_files)
|
||
|
|
||
|
logging.info('merge_cmd: %s', ' '.join(merge_cmd))
|
||
|
merge_result = subprocess.call(merge_cmd)
|
||
|
if merge_result != 0:
|
||
|
logging.warn('merge_cmd had non-zero return code: %s', merge_result)
|
||
|
|
||
|
if not os.path.exists(output_json):
|
||
|
logging.warn(
|
||
|
'merge_cmd did not create output_json file: %r', output_json)
|
||
|
|
||
|
return collect_result or merge_result
|
||
|
|
||
|
|
||
|
def main():
|
||
|
parser = argparse.ArgumentParser()
|
||
|
parser.add_argument('--build-properties')
|
||
|
parser.add_argument('--merge-additional-args')
|
||
|
parser.add_argument('--merge-script', required=True)
|
||
|
parser.add_argument('--task-output-dir', required=True)
|
||
|
parser.add_argument('-o', '--output-json', required=True)
|
||
|
parser.add_argument('--verbose', action='store_true')
|
||
|
parser.add_argument('collect_cmd', nargs='+')
|
||
|
|
||
|
args = parser.parse_args()
|
||
|
if args.verbose:
|
||
|
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
||
|
|
||
|
return collect_task(
|
||
|
args.collect_cmd,
|
||
|
args.merge_script, args.build_properties, args.merge_additional_args,
|
||
|
args.task_output_dir, args.output_json)
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
sys.exit(main())
|