xref: /aosp_15_r20/external/cronet/testing/trigger_scripts/chromeos_device_trigger.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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=
63        'Dimensions to filter on. Duplicated from the `swarming.py trigger` '
64        'command. Parsed here to ensure `device_os` is not added.')
65    parser.add_argument(
66        '--optional-dimension',
67        default=[],
68        action='append',
69        nargs=3,
70        dest='optional_dimensions',
71        help='Optional dimensions which will result in additional task slices. '
72        'Duplicated from the `swarming.py trigger` command.')
73    base_test_triggerer.BaseTestTriggerer.setup_parser_contract(parser)
74    args, additional_args = parser.parse_known_args()
75    additional_args = triggerer.modify_args(additional_args, 0,
76                                            args.shard_index, args.shards,
77                                            args.dump_json)
78
79    if additional_args[0] != 'trigger':
80        parser.error(
81            'This script is only supported for `swarming.py trigger`'
82            ' invocations.'
83        )
84
85    for k, _ in args.dimensions:
86        if k == 'device_os':
87            parser.error(
88                'Must not specify the device_os dimension when using this'
89                ' script. (It will be added automatically.)')
90
91    # It might be a valid use-case to include optional-dimensions in the initial
92    # invocation. But it'd be difficult to integrate them into what we're doing
93    # here. So let's just ensure there aren't any.
94    if args.optional_dimensions:
95        parser.error(
96            'Must not specify optional dimensions when using this script.')
97
98    return args, additional_args
99
100
101def main():
102    triggerer = base_test_triggerer.BaseTestTriggerer()
103    args, additional_args = parse_args(triggerer)
104
105    current_lkgm = read_current_lkgm()
106    if not current_lkgm:
107        return 1
108
109    new_args = additional_args[:1]
110    # Insert our modified dimension args in between the 1st and 2nd args of the
111    # initial `swarming.py` invocation. This avoids the presence of the special
112    # `--` arg from causing swarming.py to ignore them.
113    needs_device_status = True
114    for k, v in args.dimensions:
115        new_args.extend(['--dimension', k, v])
116        if k == 'device_status':
117            needs_device_status = False
118
119    # Only CrOS device bots with a device_status dimension of "available" should
120    # run tests. So target those explicitly if we aren't already.
121    if needs_device_status:
122        new_args.extend(['--dimension', 'device_status', 'available'])
123
124    new_args.extend([
125        '-optional-dimension',
126        'device_os=%s:%d' % (current_lkgm, PRIMARY_SLICE_EXPIRATION_S),
127    ])
128    new_args += additional_args[1:]
129
130    return triggerer.run_swarming_go(new_args, args.dump_json,
131                                     args.shard_index or 0, args.shards)
132
133
134if __name__ == '__main__':
135    sys.exit(main())
136