1#!/usr/bin/env vpython3 2# Copyright 2021 The ANGLE Project 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"""Script to generate the test spec JSON files. Calls Chromium's generate_buildbot_json. 6 7=== NOTE: DO NOT RUN THIS SCRIPT DIRECTLY. === 8Run scripts/run_code_generation.py instead to update necessary hashes. 9 10""" 11 12import os 13import pprint 14import sys 15import subprocess 16import tempfile 17 18d = os.path.dirname 19THIS_DIR = d(os.path.abspath(__file__)) 20TESTING_BBOT_DIR = os.path.join(d(d(THIS_DIR)), 'testing', 'buildbot') 21sys.path.insert(0, TESTING_BBOT_DIR) 22 23import generate_buildbot_json 24 25# Add custom mixins here. 26ADDITIONAL_MIXINS = { 27 'angle_skia_gold_test': { 28 'args': [ 29 '--git-revision=${got_angle_revision}', 30 # BREAK GLASS IN CASE OF EMERGENCY 31 # Uncommenting this argument will bypass all interactions with Skia 32 # Gold in any tests that use it. This is meant as a temporary 33 # emergency stop in case of a Gold outage that's affecting the bots. 34 # '--bypass-skia-gold-functionality', 35 ], 36 'precommit_args': [ 37 '--gerrit-issue=${patch_issue}', 38 '--gerrit-patchset=${patch_set}', 39 '--buildbucket-id=${buildbucket_build_id}', 40 # This normally evaluates to "0", but will evaluate to "1" if 41 # "Use-Permissive-Angle-Pixel-Comparison: True" is present as a 42 # CL footer. 43 '--use-permissive-pixel-comparison=${use_permissive_angle_pixel_comparison}', 44 ], 45 }, 46 'samsung_s22': { 47 'swarming': { 48 'dimensions': { 49 'device_os': 'UP1A.231005.007', 50 'device_os_type': 'user', 51 'device_type': 's5e9925', 52 'os': 'Android' 53 } 54 } 55 }, 56 'timeout_120m': { 57 'swarming': { 58 'hard_timeout': 7200, 59 'io_timeout': 300 60 } 61 }, 62 'temp_band_below_30C': { 63 'swarming': { 64 'dimensions': { 65 'temp_band': '<30' 66 } 67 } 68 }, 69} 70 71MIXIN_FILE_NAME = os.path.join(THIS_DIR, 'mixins.pyl') 72MIXINS_PYL_TEMPLATE = """\ 73# GENERATED FILE - DO NOT EDIT. 74# Generated by {script_name} using data from {data_source} 75# 76# Copyright 2021 The ANGLE Project Authors. All rights reserved. 77# Use of this source code is governed by a BSD-style license that can be 78# found in the LICENSE file. 79# 80# This is a .pyl, or "Python Literal", file. You can treat it just like a 81# .json file, with the following exceptions: 82# * all keys must be quoted (use single quotes, please); 83# * comments are allowed, using '#' syntax; and 84# * trailing commas are allowed. 85# 86# For more info see Chromium's mixins.pyl in testing/buildbot. 87 88{mixin_data} 89""" 90 91 92def main(): 93 if len(sys.argv) > 1: 94 gen_bb_json = os.path.join(TESTING_BBOT_DIR, 'generate_buildbot_json.py') 95 mixins_pyl = os.path.join(TESTING_BBOT_DIR, 'mixins.pyl') 96 inputs = [ 97 'test_suite_exceptions.pyl', 'test_suites.pyl', 'variants.pyl', 'waterfalls.pyl', 98 gen_bb_json, mixins_pyl 99 ] 100 outputs = ['angle.json', 'mixins.pyl'] 101 if sys.argv[1] == 'inputs': 102 print(','.join(inputs)) 103 return 0 104 if sys.argv[1] == 'outputs': 105 print(','.join(outputs)) 106 return 0 107 108 # --verify-only enables dirty checks without relying on checked in hashes. 109 # Compares the content of the existing file with the generated content. 110 verify_only = '--verify-only' in sys.argv 111 112 if verify_only: 113 with tempfile.TemporaryDirectory() as temp_dir: 114 return run_generator(verify_only, temp_dir) 115 else: 116 return run_generator(verify_only, None) 117 118 119def write_or_verify_file(filename, content, verify_only): 120 if verify_only: 121 try: 122 with open(filename) as f: 123 # Note: .gitattributes "* text=auto" handles LF <-> CRLF on Windows 124 return f.read() == content 125 except FileNotFoundError: 126 return False 127 else: 128 with open(filename, 'w') as fout: 129 fout.write(content) 130 return True 131 132 133def run_generator(verify_only, temp_dir): 134 override_args = ['--pyl-files-dir', THIS_DIR] 135 if verify_only: 136 override_args += ['--output-dir', temp_dir] 137 args = generate_buildbot_json.BBJSONGenerator.parse_args(override_args) 138 generator = generate_buildbot_json.BBJSONGenerator(args) 139 generator.load_configuration_files() 140 generator.resolve_configuration_files() 141 142 chromium_mixins = generator.load_pyl_file(os.path.join(TESTING_BBOT_DIR, 'mixins.pyl')) 143 144 seen_mixins = set() 145 for waterfall in generator.waterfalls: 146 seen_mixins = seen_mixins.union(waterfall.get('mixins', set())) 147 for bot_name, tester in waterfall['machines'].items(): 148 seen_mixins = seen_mixins.union(tester.get('mixins', set())) 149 for suite in generator.test_suites.values(): 150 if isinstance(suite, list): 151 # Don't care about this, it's a composition, which shouldn't include a 152 # swarming mixin. 153 continue 154 for test in suite.values(): 155 assert isinstance(test, dict) 156 seen_mixins = seen_mixins.union(test.get('mixins', set())) 157 158 found_mixins = ADDITIONAL_MIXINS.copy() 159 for mixin in seen_mixins: 160 if mixin in found_mixins: 161 continue 162 assert (mixin in chromium_mixins), 'Error with %s mixin' % mixin 163 found_mixins[mixin] = chromium_mixins[mixin] 164 165 pp = pprint.PrettyPrinter(indent=2) 166 167 format_data = { 168 'script_name': os.path.basename(__file__), 169 'data_source': 'waterfall.pyl and Chromium\'s mixins.pyl', 170 'mixin_data': pp.pformat(found_mixins), 171 } 172 generated_mixin_pyl = MIXINS_PYL_TEMPLATE.format(**format_data) 173 174 if not write_or_verify_file(MIXIN_FILE_NAME, generated_mixin_pyl, verify_only): 175 print('infra/specs/mixins.pyl dirty') 176 return 1 177 178 if generator.main() != 0: 179 print('buildbot (pyl to json) generation failed') 180 return 1 181 182 if verify_only: 183 for waterfall in generator.waterfalls: 184 filename = waterfall['name'] + '.json' # angle.json, might have more in future 185 with open(os.path.join(temp_dir, filename)) as f: 186 content = f.read() 187 angle_filename = os.path.join(THIS_DIR, filename) 188 if not write_or_verify_file(angle_filename, content, True): 189 print('infra/specs/%s dirty' % filename) 190 return 1 191 192 return 0 193 194 195if __name__ == '__main__': # pragma: no cover 196 sys.exit(main()) 197