1#!/usr/bin/env python3 2# Copyright 2019 The Chromium Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5"""Custom swarming trigger script for ChromeOS device tests. 6 7CrOS device tests are unique in that the device OS they prefer to run on is 8continuously changing. The LKGM file, checked into src at 9//chromeos/CHROMEOS_LKGM, represents the ChromeOS version Chrome's ToT aims 10to be compatible with. Therefore, a CrOS test for Chrome ideally targets a 11device running the LKGM. 12 13Since the LKGM file gets updated frequently (~daily), we can't reasonably 14hardcode the LKGM in the test specs. So this special trigger script will read 15the current LKGM (at the time of trigger) and append that to the task's 16dimensions. If such a device isn't available in time, the task will fallback 17to one running any OS. 18""" 19 20import argparse 21import os 22import re 23import sys 24 25import base_test_triggerer 26 27SRC_DIR = os.path.dirname( 28 os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 29LKGM_FILE_PATH = os.path.join(SRC_DIR, 'chromeos', 'CHROMEOS_LKGM') 30# Should match something that looks like "12345.0.0". 31LKGM_RE = re.compile(r'\d+\.\d+\.\d+') 32PRIMARY_SLICE_EXPIRATION_S = 300 33 34 35def read_current_lkgm(): 36 if not os.path.exists(LKGM_FILE_PATH): 37 sys.stderr.write('LKGM file not present at %s\n' % LKGM_FILE_PATH) 38 return None 39 40 with open(LKGM_FILE_PATH) as f: 41 lkgm = f.read().strip() 42 43 if not LKGM_RE.match(lkgm): 44 sys.stderr.write('Unknown format of LKGM: %s\n' % lkgm) 45 return None 46 47 # Just the major version should be sufficient. 48 return lkgm.split('.')[0] 49 50 51def parse_args(triggerer): 52 # This script will do nothing but inspect and tweak the dimension args to 53 # `swarming.py trigger`. So let's pull just those out. 54 parser = argparse.ArgumentParser(description=__doc__) 55 parser.add_argument( 56 '-d', 57 '--dimension', 58 default=[], 59 action='append', 60 nargs=2, 61 dest='dimensions', 62 help='Dimensions to filter on. Duplicated from the `swarming.py ' 63 'trigger` command. Parsed here to ensure `device_os` is not added.') 64 parser.add_argument( 65 '--optional-dimension', 66 default=[], 67 action='append', 68 nargs=3, 69 dest='optional_dimensions', 70 help='Optional dimensions which will result in additional task slices. ' 71 'Duplicated from the `swarming.py trigger` command.') 72 base_test_triggerer.BaseTestTriggerer.setup_parser_contract(parser) 73 args, additional_args = parser.parse_known_args() 74 additional_args = triggerer.modify_args(additional_args, 0, 75 args.shard_index, args.shards, 76 args.dump_json) 77 78 if additional_args[0] != 'trigger': 79 parser.error('This script is only supported for `swarming.py trigger`' 80 ' invocations.') 81 82 for k, _ in args.dimensions: 83 if k == 'device_os': 84 parser.error( 85 'Must not specify the device_os dimension when using this' 86 ' script. (It will be added automatically.)') 87 88 # It might be a valid use-case to include optional-dimensions in the initial 89 # invocation. But it'd be difficult to integrate them into what we're doing 90 # here. So let's just ensure there aren't any. 91 if args.optional_dimensions: 92 parser.error( 93 'Must not specify optional dimensions when using this script.') 94 95 return args, additional_args 96 97 98def main(): 99 triggerer = base_test_triggerer.BaseTestTriggerer() 100 args, additional_args = parse_args(triggerer) 101 102 current_lkgm = read_current_lkgm() 103 if not current_lkgm: 104 return 1 105 106 new_args = additional_args[:1] 107 # Insert our modified dimension args in between the 1st and 2nd args of the 108 # initial `swarming.py` invocation. This avoids the presence of the special 109 # `--` arg from causing swarming.py to ignore them. 110 needs_device_status = True 111 for k, v in args.dimensions: 112 new_args.extend(['--dimension', k, v]) 113 if k == 'device_status': 114 needs_device_status = False 115 116 # Only CrOS device bots with a device_status dimension of "available" should 117 # run tests. So target those explicitly if we aren't already. 118 if needs_device_status: 119 new_args.extend(['--dimension', 'device_status', 'available']) 120 121 new_args.extend([ 122 '-optional-dimension', 123 'device_os=%s:%d' % (current_lkgm, PRIMARY_SLICE_EXPIRATION_S), 124 ]) 125 new_args += additional_args[1:] 126 127 return triggerer.run_swarming_go(new_args, args.dump_json, args.shard_index 128 or 0, args.shards) 129 130 131if __name__ == '__main__': 132 sys.exit(main()) 133