Home | History | Annotate | Download | only in resources
      1 #!/usr/bin/env python
      2 # Copyright 2017 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 import argparse
      7 import json
      8 import logging
      9 import os
     10 import subprocess
     11 import sys
     12 
     13 
     14 def collect_task(
     15     collect_cmd, merge_script, build_properties, merge_arguments,
     16     task_output_dir, output_json):
     17   """Collect and merge the results of a task.
     18 
     19   This is a relatively thin wrapper script around a `swarming.py collect`
     20   command and a subsequent results merge to ensure that the recipe system
     21   treats them as a single step. The results merge can either be the default
     22   one provided by results_merger or a python script provided as merge_script.
     23 
     24   Args:
     25     collect_cmd: The `swarming.py collect` command to run. Should not contain
     26       a --task-output-dir argument.
     27     merge_script: A merge/postprocessing script that should be run to
     28       merge the results. This script will be invoked as
     29 
     30         <merge_script> \
     31             [--build-properties <string JSON>] \
     32             [merge arguments...] \
     33             --summary-json <summary json> \
     34             -o <merged json path> \
     35             <shard json>...
     36 
     37       where the merge arguments are the contents of merge_arguments_json.
     38     build_properties: A string containing build information to
     39       pass to the merge script in JSON form.
     40     merge_arguments: A string containing additional arguments to pass to
     41       the merge script in JSON form.
     42     task_output_dir: A path to a directory in which swarming will write the
     43       output of the task, including a summary JSON and all of the individual
     44       shard results.
     45     output_json: A path to a JSON file to which the merged results should be
     46       written. The merged results should be in the JSON Results File Format
     47       (https://www.chromium.org/developers/the-json-test-results-format)
     48       and may optionally contain a top level "links" field that may contain a
     49       dict mapping link text to URLs, for a set of links that will be included
     50       in the buildbot output.
     51   Returns:
     52     The exit code of collect_cmd or merge_cmd.
     53   """
     54   logging.debug('Using task_output_dir: %r', task_output_dir)
     55   if os.path.exists(task_output_dir):
     56     logging.warn('task_output_dir %r already exists!', task_output_dir)
     57     existing_contents = []
     58     try:
     59       for p in os.listdir(task_output_dir):
     60         existing_contents.append(os.path.join(task_output_dir, p))
     61     except (OSError, IOError) as e:
     62       logging.error('Error while examining existing task_output_dir: %s', e)
     63 
     64     logging.warn('task_output_dir existing content: %r', existing_contents)
     65 
     66   collect_cmd.extend(['--task-output-dir', task_output_dir])
     67 
     68   logging.info('collect_cmd: %s', ' '.join(collect_cmd))
     69   collect_result = subprocess.call(collect_cmd)
     70   if collect_result != 0:
     71     logging.warn('collect_cmd had non-zero return code: %s', collect_result)
     72 
     73   task_output_dir_contents = []
     74   try:
     75     task_output_dir_contents.extend(
     76         os.path.join(task_output_dir, p)
     77         for p in os.listdir(task_output_dir))
     78   except (OSError, IOError) as e:
     79     logging.error('Error while processing task_output_dir: %s', e)
     80 
     81   logging.debug('Contents of task_output_dir: %r', task_output_dir_contents)
     82   if not task_output_dir_contents:
     83     logging.warn(
     84         'No files found in task_output_dir: %r',
     85         task_output_dir)
     86 
     87   task_output_subdirs = (
     88       p for p in task_output_dir_contents
     89       if os.path.isdir(p))
     90   shard_json_files = [
     91       os.path.join(subdir, 'output.json')
     92       for subdir in task_output_subdirs]
     93   extant_shard_json_files = [
     94       f for f in shard_json_files if os.path.exists(f)]
     95 
     96   if shard_json_files != extant_shard_json_files:
     97     logging.warn(
     98         'Expected output.json file missing: %r\nFound: %r\nExpected: %r\n',
     99         set(shard_json_files) - set(extant_shard_json_files),
    100         extant_shard_json_files,
    101         shard_json_files)
    102 
    103   if not extant_shard_json_files:
    104     logging.warn(
    105         'No shard json files found in task_output_dir: %r\nFound %r',
    106         task_output_dir, task_output_dir_contents)
    107 
    108   logging.debug('Found shard_json_files: %r', shard_json_files)
    109 
    110   summary_json_file = os.path.join(task_output_dir, 'summary.json')
    111 
    112   merge_result = 0
    113 
    114   merge_cmd = [sys.executable, merge_script]
    115   if build_properties:
    116     merge_cmd.extend(('--build-properties', build_properties))
    117   if os.path.exists(summary_json_file):
    118     merge_cmd.extend(('--summary-json', summary_json_file))
    119   else:
    120     logging.warn('Summary json file missing: %r', summary_json_file)
    121   if merge_arguments:
    122     merge_cmd.extend(json.loads(merge_arguments))
    123   merge_cmd.extend(('-o', output_json))
    124   merge_cmd.extend(extant_shard_json_files)
    125 
    126   logging.info('merge_cmd: %s', ' '.join(merge_cmd))
    127   merge_result = subprocess.call(merge_cmd)
    128   if merge_result != 0:
    129     logging.warn('merge_cmd had non-zero return code: %s', merge_result)
    130 
    131   if not os.path.exists(output_json):
    132     logging.warn(
    133         'merge_cmd did not create output_json file: %r', output_json)
    134 
    135   return collect_result or merge_result
    136 
    137 
    138 def main():
    139   parser = argparse.ArgumentParser()
    140   parser.add_argument('--build-properties')
    141   parser.add_argument('--merge-additional-args')
    142   parser.add_argument('--merge-script', required=True)
    143   parser.add_argument('--task-output-dir', required=True)
    144   parser.add_argument('-o', '--output-json', required=True)
    145   parser.add_argument('--verbose', action='store_true')
    146   parser.add_argument('collect_cmd', nargs='+')
    147 
    148   args = parser.parse_args()
    149   if args.verbose:
    150     logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
    151 
    152   return collect_task(
    153       args.collect_cmd,
    154       args.merge_script, args.build_properties, args.merge_additional_args,
    155       args.task_output_dir, args.output_json)
    156 
    157 
    158 if __name__ == '__main__':
    159   sys.exit(main())
    160