1*8975f5c5SAndroid Build Coastguard Worker#!/usr/bin/env vpython 2*8975f5c5SAndroid Build Coastguard Worker# 3*8975f5c5SAndroid Build Coastguard Worker# [VPYTHON:BEGIN] 4*8975f5c5SAndroid Build Coastguard Worker# wheel: < 5*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/google-auth-py2_py3" 6*8975f5c5SAndroid Build Coastguard Worker# version: "version:1.2.1" 7*8975f5c5SAndroid Build Coastguard Worker# > 8*8975f5c5SAndroid Build Coastguard Worker# 9*8975f5c5SAndroid Build Coastguard Worker# wheel: < 10*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/pyasn1-py2_py3" 11*8975f5c5SAndroid Build Coastguard Worker# version: "version:0.4.5" 12*8975f5c5SAndroid Build Coastguard Worker# > 13*8975f5c5SAndroid Build Coastguard Worker# 14*8975f5c5SAndroid Build Coastguard Worker# wheel: < 15*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/pyasn1_modules-py2_py3" 16*8975f5c5SAndroid Build Coastguard Worker# version: "version:0.2.4" 17*8975f5c5SAndroid Build Coastguard Worker# > 18*8975f5c5SAndroid Build Coastguard Worker# 19*8975f5c5SAndroid Build Coastguard Worker# wheel: < 20*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/six" 21*8975f5c5SAndroid Build Coastguard Worker# version: "version:1.10.0" 22*8975f5c5SAndroid Build Coastguard Worker# > 23*8975f5c5SAndroid Build Coastguard Worker# 24*8975f5c5SAndroid Build Coastguard Worker# wheel: < 25*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/cachetools-py2_py3" 26*8975f5c5SAndroid Build Coastguard Worker# version: "version:2.0.1" 27*8975f5c5SAndroid Build Coastguard Worker# > 28*8975f5c5SAndroid Build Coastguard Worker# wheel: < 29*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/rsa-py2_py3" 30*8975f5c5SAndroid Build Coastguard Worker# version: "version:4.0" 31*8975f5c5SAndroid Build Coastguard Worker# > 32*8975f5c5SAndroid Build Coastguard Worker# 33*8975f5c5SAndroid Build Coastguard Worker# wheel: < 34*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/requests" 35*8975f5c5SAndroid Build Coastguard Worker# version: "version:2.13.0" 36*8975f5c5SAndroid Build Coastguard Worker# > 37*8975f5c5SAndroid Build Coastguard Worker# 38*8975f5c5SAndroid Build Coastguard Worker# wheel: < 39*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/google-api-python-client-py2_py3" 40*8975f5c5SAndroid Build Coastguard Worker# version: "version:1.6.2" 41*8975f5c5SAndroid Build Coastguard Worker# > 42*8975f5c5SAndroid Build Coastguard Worker# 43*8975f5c5SAndroid Build Coastguard Worker# wheel: < 44*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/httplib2-py2_py3" 45*8975f5c5SAndroid Build Coastguard Worker# version: "version:0.12.1" 46*8975f5c5SAndroid Build Coastguard Worker# > 47*8975f5c5SAndroid Build Coastguard Worker# 48*8975f5c5SAndroid Build Coastguard Worker# wheel: < 49*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/oauth2client-py2_py3" 50*8975f5c5SAndroid Build Coastguard Worker# version: "version:3.0.0" 51*8975f5c5SAndroid Build Coastguard Worker# > 52*8975f5c5SAndroid Build Coastguard Worker# 53*8975f5c5SAndroid Build Coastguard Worker# wheel: < 54*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/uritemplate-py2_py3" 55*8975f5c5SAndroid Build Coastguard Worker# version: "version:3.0.0" 56*8975f5c5SAndroid Build Coastguard Worker# > 57*8975f5c5SAndroid Build Coastguard Worker# 58*8975f5c5SAndroid Build Coastguard Worker# wheel: < 59*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/google-auth-oauthlib-py2_py3" 60*8975f5c5SAndroid Build Coastguard Worker# version: "version:0.3.0" 61*8975f5c5SAndroid Build Coastguard Worker# > 62*8975f5c5SAndroid Build Coastguard Worker# 63*8975f5c5SAndroid Build Coastguard Worker# wheel: < 64*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/requests-oauthlib-py2_py3" 65*8975f5c5SAndroid Build Coastguard Worker# version: "version:1.2.0" 66*8975f5c5SAndroid Build Coastguard Worker# > 67*8975f5c5SAndroid Build Coastguard Worker# 68*8975f5c5SAndroid Build Coastguard Worker# wheel: < 69*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/oauthlib-py2_py3" 70*8975f5c5SAndroid Build Coastguard Worker# version: "version:3.0.1" 71*8975f5c5SAndroid Build Coastguard Worker# > 72*8975f5c5SAndroid Build Coastguard Worker# 73*8975f5c5SAndroid Build Coastguard Worker# wheel: < 74*8975f5c5SAndroid Build Coastguard Worker# name: "infra/python/wheels/google-auth-httplib2-py2_py3" 75*8975f5c5SAndroid Build Coastguard Worker# version: "version:0.0.3" 76*8975f5c5SAndroid Build Coastguard Worker# > 77*8975f5c5SAndroid Build Coastguard Worker# [VPYTHON:END] 78*8975f5c5SAndroid Build Coastguard Worker# 79*8975f5c5SAndroid Build Coastguard Worker# Copyright 2019 The ANGLE Project Authors. All rights reserved. 80*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be 81*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file. 82*8975f5c5SAndroid Build Coastguard Worker# 83*8975f5c5SAndroid Build Coastguard Worker# generate_deqp_stats.py: 84*8975f5c5SAndroid Build Coastguard Worker# Checks output of deqp testers and generates stats using the GDocs API 85*8975f5c5SAndroid Build Coastguard Worker# 86*8975f5c5SAndroid Build Coastguard Worker# prerequirements: 87*8975f5c5SAndroid Build Coastguard Worker# https://devsite.googleplex.com/sheets/api/quickstart/python 88*8975f5c5SAndroid Build Coastguard Worker# Follow the quickstart guide. 89*8975f5c5SAndroid Build Coastguard Worker# 90*8975f5c5SAndroid Build Coastguard Worker# usage: generate_deqp_stats.py [-h] [--auth_path [AUTH_PATH]] [--spreadsheet [SPREADSHEET]] 91*8975f5c5SAndroid Build Coastguard Worker# [--verbosity [VERBOSITY]] 92*8975f5c5SAndroid Build Coastguard Worker# 93*8975f5c5SAndroid Build Coastguard Worker# optional arguments: 94*8975f5c5SAndroid Build Coastguard Worker# -h, --help show this help message and exit 95*8975f5c5SAndroid Build Coastguard Worker# --auth_path [AUTH_PATH] 96*8975f5c5SAndroid Build Coastguard Worker# path to directory containing authorization data (credentials.json and 97*8975f5c5SAndroid Build Coastguard Worker# token.pickle). [default=<home>/.auth] 98*8975f5c5SAndroid Build Coastguard Worker# --spreadsheet [SPREADSHEET] 99*8975f5c5SAndroid Build Coastguard Worker# ID of the spreadsheet to write stats to. [default 100*8975f5c5SAndroid Build Coastguard Worker# ='1D6Yh7dAPP-aYLbX3HHQD8WubJV9XPuxvkKowmn2qhIw'] 101*8975f5c5SAndroid Build Coastguard Worker# --verbosity [VERBOSITY] 102*8975f5c5SAndroid Build Coastguard Worker# Verbosity of output. Valid options are [DEBUG, INFO, WARNING, ERROR]. 103*8975f5c5SAndroid Build Coastguard Worker# [default=INFO] 104*8975f5c5SAndroid Build Coastguard Worker 105*8975f5c5SAndroid Build Coastguard Workerimport argparse 106*8975f5c5SAndroid Build Coastguard Workerimport datetime 107*8975f5c5SAndroid Build Coastguard Workerimport logging 108*8975f5c5SAndroid Build Coastguard Workerimport os 109*8975f5c5SAndroid Build Coastguard Workerimport pickle 110*8975f5c5SAndroid Build Coastguard Workerimport re 111*8975f5c5SAndroid Build Coastguard Workerimport subprocess 112*8975f5c5SAndroid Build Coastguard Workerimport sys 113*8975f5c5SAndroid Build Coastguard Workerimport urllib 114*8975f5c5SAndroid Build Coastguard Workerfrom google.auth.transport.requests import Request 115*8975f5c5SAndroid Build Coastguard Workerfrom googleapiclient.discovery import build 116*8975f5c5SAndroid Build Coastguard Workerfrom google_auth_oauthlib.flow import InstalledAppFlow 117*8975f5c5SAndroid Build Coastguard Worker 118*8975f5c5SAndroid Build Coastguard Worker#################### 119*8975f5c5SAndroid Build Coastguard Worker# Global Constants # 120*8975f5c5SAndroid Build Coastguard Worker#################### 121*8975f5c5SAndroid Build Coastguard Worker 122*8975f5c5SAndroid Build Coastguard WorkerHOME_DIR = os.path.expanduser('~') 123*8975f5c5SAndroid Build Coastguard WorkerSCRIPT_DIR = sys.path[0] 124*8975f5c5SAndroid Build Coastguard WorkerROOT_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, '..')) 125*8975f5c5SAndroid Build Coastguard Worker 126*8975f5c5SAndroid Build Coastguard WorkerLOGGER = logging.getLogger('generate_stats') 127*8975f5c5SAndroid Build Coastguard Worker 128*8975f5c5SAndroid Build Coastguard WorkerSCOPES = ['https://www.googleapis.com/auth/spreadsheets'] 129*8975f5c5SAndroid Build Coastguard Worker 130*8975f5c5SAndroid Build Coastguard WorkerBOT_NAMES = [ 131*8975f5c5SAndroid Build Coastguard Worker 'mac-angle-amd', 132*8975f5c5SAndroid Build Coastguard Worker 'mac-angle-intel', 133*8975f5c5SAndroid Build Coastguard Worker 'win10-angle-x64-nvidia', 134*8975f5c5SAndroid Build Coastguard Worker 'win10-angle-x64-intel', 135*8975f5c5SAndroid Build Coastguard Worker 'win7-angle-x64-nvidia', 136*8975f5c5SAndroid Build Coastguard Worker 'win7-angle-x86-amd', 137*8975f5c5SAndroid Build Coastguard Worker 'Linux FYI dEQP Release (Intel HD 630)', 138*8975f5c5SAndroid Build Coastguard Worker 'Linux FYI dEQP Release (NVIDIA)', 139*8975f5c5SAndroid Build Coastguard Worker 'Android FYI dEQP Release (Nexus 5X)', 140*8975f5c5SAndroid Build Coastguard Worker 'Android FYI 32 dEQP Vk Release (Pixel 2)', 141*8975f5c5SAndroid Build Coastguard Worker 'Android FYI 64 dEQP Vk Release (Pixel 2)', 142*8975f5c5SAndroid Build Coastguard Worker] 143*8975f5c5SAndroid Build Coastguard WorkerBOT_NAME_PREFIX = 'chromium/ci/' 144*8975f5c5SAndroid Build Coastguard WorkerBUILD_LINK_PREFIX = 'https://ci.chromium.org/p/chromium/builders/ci/' 145*8975f5c5SAndroid Build Coastguard Worker 146*8975f5c5SAndroid Build Coastguard WorkerREQUIRED_COLUMNS = ['build_link', 'time', 'date', 'revision', 'angle_revision', 'duplicate'] 147*8975f5c5SAndroid Build Coastguard WorkerMAIN_RESULT_COLUMNS = ['Passed', 'Failed', 'Skipped', 'Not Supported', 'Exception', 'Crashed'] 148*8975f5c5SAndroid Build Coastguard Worker 149*8975f5c5SAndroid Build Coastguard WorkerINFO_TAG = '*RESULT' 150*8975f5c5SAndroid Build Coastguard Worker 151*8975f5c5SAndroid Build Coastguard WorkerWORKAROUND_FORMATTING_ERROR_STRING = "Still waiting for the following processes to finish:" 152*8975f5c5SAndroid Build Coastguard Worker 153*8975f5c5SAndroid Build Coastguard Worker###################### 154*8975f5c5SAndroid Build Coastguard Worker# Build Info Parsing # 155*8975f5c5SAndroid Build Coastguard Worker###################### 156*8975f5c5SAndroid Build Coastguard Worker 157*8975f5c5SAndroid Build Coastguard Worker 158*8975f5c5SAndroid Build Coastguard Worker# Returns a struct with info about the latest successful build given a bot name. Info contains the 159*8975f5c5SAndroid Build Coastguard Worker# build_name, time, date, angle_revision, and chrome revision. 160*8975f5c5SAndroid Build Coastguard Worker# Uses: bb ls '<botname>' -n 1 -status success -p 161*8975f5c5SAndroid Build Coastguard Workerdef get_latest_success_build_info(bot_name): 162*8975f5c5SAndroid Build Coastguard Worker bb = subprocess.Popen(['bb', 'ls', bot_name, '-n', '1', '-status', 'success', '-p'], 163*8975f5c5SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 164*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 165*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Ran [bb ls '" + bot_name + "' -n 1 -status success -p]") 166*8975f5c5SAndroid Build Coastguard Worker out, err = bb.communicate() 167*8975f5c5SAndroid Build Coastguard Worker if err: 168*8975f5c5SAndroid Build Coastguard Worker raise ValueError("Unexpected error from bb ls: '" + err + "'") 169*8975f5c5SAndroid Build Coastguard Worker if not out: 170*8975f5c5SAndroid Build Coastguard Worker raise ValueError("Unexpected empty result from bb ls of bot '" + bot_name + "'") 171*8975f5c5SAndroid Build Coastguard Worker # Example output (line 1): 172*8975f5c5SAndroid Build Coastguard Worker # ci.chromium.org/b/8915280275579996928 SUCCESS 'chromium/ci/Win10 FYI dEQP Release (NVIDIA)/26877' 173*8975f5c5SAndroid Build Coastguard Worker # ... 174*8975f5c5SAndroid Build Coastguard Worker if 'SUCCESS' not in out: 175*8975f5c5SAndroid Build Coastguard Worker raise ValueError("Unexpected result from bb ls: '" + out + "'") 176*8975f5c5SAndroid Build Coastguard Worker info = {} 177*8975f5c5SAndroid Build Coastguard Worker for line in out.splitlines(): 178*8975f5c5SAndroid Build Coastguard Worker # The first line holds the build name 179*8975f5c5SAndroid Build Coastguard Worker if 'build_name' not in info: 180*8975f5c5SAndroid Build Coastguard Worker info['build_name'] = line.strip().split("'")[1] 181*8975f5c5SAndroid Build Coastguard Worker # Remove the bot name and prepend the build link 182*8975f5c5SAndroid Build Coastguard Worker info['build_link'] = BUILD_LINK_PREFIX + urllib.quote( 183*8975f5c5SAndroid Build Coastguard Worker info['build_name'].split(BOT_NAME_PREFIX)[1]) 184*8975f5c5SAndroid Build Coastguard Worker if 'Created' in line: 185*8975f5c5SAndroid Build Coastguard Worker # Example output of line with 'Created': 186*8975f5c5SAndroid Build Coastguard Worker # ... 187*8975f5c5SAndroid Build Coastguard Worker # Created today at 12:26:39, waited 2.056319s, started at 12:26:41, ran for 1h16m48.14963s, ended at 13:43:30 188*8975f5c5SAndroid Build Coastguard Worker # ... 189*8975f5c5SAndroid Build Coastguard Worker info['time'] = re.findall(r'[0-9]{1,2}:[0-9]{2}:[0-9]{2}', line.split(',', 1)[0])[0] 190*8975f5c5SAndroid Build Coastguard Worker # Format today's date in US format so Sheets can read it properly 191*8975f5c5SAndroid Build Coastguard Worker info['date'] = datetime.datetime.now().strftime('%m/%d/%y') 192*8975f5c5SAndroid Build Coastguard Worker if 'got_angle_revision' in line: 193*8975f5c5SAndroid Build Coastguard Worker # Example output of line with angle revision: 194*8975f5c5SAndroid Build Coastguard Worker # ... 195*8975f5c5SAndroid Build Coastguard Worker # "parent_got_angle_revision": "8cbd321cafa92ffbf0495e6d0aeb9e1a97940fee", 196*8975f5c5SAndroid Build Coastguard Worker # ... 197*8975f5c5SAndroid Build Coastguard Worker info['angle_revision'] = filter(str.isalnum, line.split(':')[1]) 198*8975f5c5SAndroid Build Coastguard Worker if '"revision"' in line: 199*8975f5c5SAndroid Build Coastguard Worker # Example output of line with chromium revision: 200*8975f5c5SAndroid Build Coastguard Worker # ... 201*8975f5c5SAndroid Build Coastguard Worker # "revision": "3b68405a27f1f9590f83ae07757589dba862f141", 202*8975f5c5SAndroid Build Coastguard Worker # ... 203*8975f5c5SAndroid Build Coastguard Worker info['revision'] = filter(str.isalnum, line.split(':')[1]) 204*8975f5c5SAndroid Build Coastguard Worker if 'build_name' not in info: 205*8975f5c5SAndroid Build Coastguard Worker raise ValueError("Could not find build_name from bot '" + bot_name + "'") 206*8975f5c5SAndroid Build Coastguard Worker return info 207*8975f5c5SAndroid Build Coastguard Worker 208*8975f5c5SAndroid Build Coastguard Worker 209*8975f5c5SAndroid Build Coastguard Worker# Returns a list of step names that we're interested in given a build name. We are interested in 210*8975f5c5SAndroid Build Coastguard Worker# step names starting with 'angle_'. May raise an exception. 211*8975f5c5SAndroid Build Coastguard Worker# Uses: bb get '<build_name>' -steps 212*8975f5c5SAndroid Build Coastguard Workerdef get_step_names(build_name): 213*8975f5c5SAndroid Build Coastguard Worker bb = subprocess.Popen(['bb', 'get', build_name, '-steps'], 214*8975f5c5SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 215*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 216*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Ran [bb get '" + build_name + "' -steps]") 217*8975f5c5SAndroid Build Coastguard Worker out, err = bb.communicate() 218*8975f5c5SAndroid Build Coastguard Worker if err: 219*8975f5c5SAndroid Build Coastguard Worker raise ValueError("Unexpected error from bb get: '" + err + "'") 220*8975f5c5SAndroid Build Coastguard Worker step_names = [] 221*8975f5c5SAndroid Build Coastguard Worker # Example output (relevant lines to a single step): 222*8975f5c5SAndroid Build Coastguard Worker # ... 223*8975f5c5SAndroid Build Coastguard Worker # Step "angle_deqp_egl_vulkan_tests on (nvidia-quadro-p400-win10-stable) GPU on Windows on Windows-10" SUCCESS 4m12s Logs: "stdout", "chromium_swarming.summary", "Merge script log", "Flaky failure: dEQP.EGL/info_version (status CRASH,SUCCESS)", "step_metadata" 224*8975f5c5SAndroid Build Coastguard Worker # Run on OS: 'Windows-10'<br>Max shard duration: 0:04:07.309848 (shard \#1)<br>Min shard duration: 0:02:26.402128 (shard \#0)<br/>flaky failures [ignored]:<br/>dEQP.EGL/info\_version<br/> 225*8975f5c5SAndroid Build Coastguard Worker # * [shard #0 isolated out](https://isolateserver.appspot.com/browse?namespace=default-gzip&hash=9a5999a59d332e55f54f495948d0c9f959e60ed2) 226*8975f5c5SAndroid Build Coastguard Worker # * [shard #0 (128.3 sec)](https://chromium-swarm.appspot.com/user/task/446903ae365b8110) 227*8975f5c5SAndroid Build Coastguard Worker # * [shard #1 isolated out](https://isolateserver.appspot.com/browse?namespace=default-gzip&hash=d71e1bdd91dee61b536b4057a9222e642bd3809f) 228*8975f5c5SAndroid Build Coastguard Worker # * [shard #1 (229.3 sec)](https://chromium-swarm.appspot.com/user/task/446903b7b0d90210) 229*8975f5c5SAndroid Build Coastguard Worker # * [shard #2 isolated out](https://isolateserver.appspot.com/browse?namespace=default-gzip&hash=ac9ba85b1cca77774061b87335c077980e1eef85) 230*8975f5c5SAndroid Build Coastguard Worker # * [shard #2 (144.5 sec)](https://chromium-swarm.appspot.com/user/task/446903c18e15a010) 231*8975f5c5SAndroid Build Coastguard Worker # * [shard #3 isolated out](https://isolateserver.appspot.com/browse?namespace=default-gzip&hash=976d586386864abecf53915fbac3e085f672e30f) 232*8975f5c5SAndroid Build Coastguard Worker # * [shard #3 (138.4 sec)](https://chromium-swarm.appspot.com/user/task/446903cc8da0ad10) 233*8975f5c5SAndroid Build Coastguard Worker # ... 234*8975f5c5SAndroid Build Coastguard Worker for line in out.splitlines(): 235*8975f5c5SAndroid Build Coastguard Worker if 'Step "angle_' not in line: 236*8975f5c5SAndroid Build Coastguard Worker continue 237*8975f5c5SAndroid Build Coastguard Worker step_names.append(line.split('"')[1]) 238*8975f5c5SAndroid Build Coastguard Worker return step_names 239*8975f5c5SAndroid Build Coastguard Worker 240*8975f5c5SAndroid Build Coastguard Worker 241*8975f5c5SAndroid Build Coastguard Worker# Performs some heuristic validation of the step_info struct returned from a single step log. 242*8975f5c5SAndroid Build Coastguard Worker# Returns True if valid, False if invalid. May write to stderr 243*8975f5c5SAndroid Build Coastguard Workerdef validate_step_info(step_info, build_name, step_name): 244*8975f5c5SAndroid Build Coastguard Worker print_name = "'" + build_name + "': '" + step_name + "'" 245*8975f5c5SAndroid Build Coastguard Worker if not step_info: 246*8975f5c5SAndroid Build Coastguard Worker LOGGER.warning('Step info empty for ' + print_name + '\n') 247*8975f5c5SAndroid Build Coastguard Worker return False 248*8975f5c5SAndroid Build Coastguard Worker 249*8975f5c5SAndroid Build Coastguard Worker if 'Total' in step_info: 250*8975f5c5SAndroid Build Coastguard Worker partial_sum_keys = MAIN_RESULT_COLUMNS 251*8975f5c5SAndroid Build Coastguard Worker partial_sum_values = [int(step_info[key]) for key in partial_sum_keys if key in step_info] 252*8975f5c5SAndroid Build Coastguard Worker computed_total = sum(partial_sum_values) 253*8975f5c5SAndroid Build Coastguard Worker if step_info['Total'] != computed_total: 254*8975f5c5SAndroid Build Coastguard Worker LOGGER.warning('Step info does not sum to total for ' + print_name + ' | Total: ' + 255*8975f5c5SAndroid Build Coastguard Worker str(step_info['Total']) + ' - Computed total: ' + str(computed_total) + 256*8975f5c5SAndroid Build Coastguard Worker '\n') 257*8975f5c5SAndroid Build Coastguard Worker return True 258*8975f5c5SAndroid Build Coastguard Worker 259*8975f5c5SAndroid Build Coastguard Worker 260*8975f5c5SAndroid Build Coastguard Worker# Returns a struct containing parsed info from a given step log. The info is parsed by looking for 261*8975f5c5SAndroid Build Coastguard Worker# lines with the following format in stdout: 262*8975f5c5SAndroid Build Coastguard Worker# '[TESTSTATS]: <key>: <value>'' 263*8975f5c5SAndroid Build Coastguard Worker# May write to stderr 264*8975f5c5SAndroid Build Coastguard Worker# Uses: bb log '<build_name>' '<step_name>' 265*8975f5c5SAndroid Build Coastguard Workerdef get_step_info(build_name, step_name): 266*8975f5c5SAndroid Build Coastguard Worker bb = subprocess.Popen(['bb', 'log', build_name, step_name], 267*8975f5c5SAndroid Build Coastguard Worker stdout=subprocess.PIPE, 268*8975f5c5SAndroid Build Coastguard Worker stderr=subprocess.PIPE) 269*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Ran [bb log '" + build_name + "' '" + step_name + "']") 270*8975f5c5SAndroid Build Coastguard Worker out, err = bb.communicate() 271*8975f5c5SAndroid Build Coastguard Worker if err: 272*8975f5c5SAndroid Build Coastguard Worker LOGGER.warning("Unexpected error from bb log '" + build_name + "' '" + step_name + "': '" + 273*8975f5c5SAndroid Build Coastguard Worker err + "'") 274*8975f5c5SAndroid Build Coastguard Worker return None 275*8975f5c5SAndroid Build Coastguard Worker step_info = {} 276*8975f5c5SAndroid Build Coastguard Worker # Example output (relevant lines of stdout): 277*8975f5c5SAndroid Build Coastguard Worker # ... 278*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Total: 155 279*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Passed: 11 280*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Failed: 0 281*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Skipped: 12 282*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Not Supported: 132 283*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Exception: 0 284*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Crashed: 0 285*8975f5c5SAndroid Build Coastguard Worker # *RESULT: Unexpected Passed: 12 286*8975f5c5SAndroid Build Coastguard Worker # ... 287*8975f5c5SAndroid Build Coastguard Worker append_errors = [] 288*8975f5c5SAndroid Build Coastguard Worker # Hacky workaround to fix issue where messages are dropped into the middle of lines by another 289*8975f5c5SAndroid Build Coastguard Worker # process: 290*8975f5c5SAndroid Build Coastguard Worker # eg. 291*8975f5c5SAndroid Build Coastguard Worker # *RESULT: <start_of_result>Still waiting for the following processes to finish: 292*8975f5c5SAndroid Build Coastguard Worker # "c:\b\s\w\ir\out\Release\angle_deqp_gles3_tests.exe" --deqp-egl-display-type=angle-vulkan --gtest_flagfile="c:\b\s\w\itlcgdrz\scoped_dir7104_364984996\8ad93729-f679-406d-973b-06b9d1bf32de.tmp" --single-process-tests --test-launcher-batch-limit=400 --test-launcher-output="c:\b\s\w\itlcgdrz\7104_437216092\test_results.xml" --test-launcher-summary-output="c:\b\s\w\iosuk8ai\output.json" 293*8975f5c5SAndroid Build Coastguard Worker # <end_of_result> 294*8975f5c5SAndroid Build Coastguard Worker # 295*8975f5c5SAndroid Build Coastguard Worker # Removes the message and skips the line following it, and then appends the <start_of_result> 296*8975f5c5SAndroid Build Coastguard Worker # and <end_of_result> back together 297*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line = "" 298*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line_count = 0 299*8975f5c5SAndroid Build Coastguard Worker for line in out.splitlines(): 300*8975f5c5SAndroid Build Coastguard Worker # Skip lines if the workaround still has lines to skip 301*8975f5c5SAndroid Build Coastguard Worker if workaround_prev_line_count > 0: 302*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line_count -= 1 303*8975f5c5SAndroid Build Coastguard Worker continue 304*8975f5c5SAndroid Build Coastguard Worker # If there are no more lines to skip and there is a previous <start_of_result> to append, 305*8975f5c5SAndroid Build Coastguard Worker # append it and finish the workaround 306*8975f5c5SAndroid Build Coastguard Worker elif workaround_prev_line != "": 307*8975f5c5SAndroid Build Coastguard Worker line = workaround_prev_line + line 308*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line = "" 309*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line_count = 0 310*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Formatting error workaround rebuilt line as: '" + line + "'\n") 311*8975f5c5SAndroid Build Coastguard Worker 312*8975f5c5SAndroid Build Coastguard Worker if INFO_TAG not in line: 313*8975f5c5SAndroid Build Coastguard Worker continue 314*8975f5c5SAndroid Build Coastguard Worker 315*8975f5c5SAndroid Build Coastguard Worker # When the workaround string is detected, start the workaround with 1 line to skip and save 316*8975f5c5SAndroid Build Coastguard Worker # the <start_of_result>, but continue the loop until the workaround is finished 317*8975f5c5SAndroid Build Coastguard Worker if WORKAROUND_FORMATTING_ERROR_STRING in line: 318*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line = line.split(WORKAROUND_FORMATTING_ERROR_STRING)[0] 319*8975f5c5SAndroid Build Coastguard Worker workaround_prev_line_count = 1 320*8975f5c5SAndroid Build Coastguard Worker continue 321*8975f5c5SAndroid Build Coastguard Worker 322*8975f5c5SAndroid Build Coastguard Worker found_stat = True 323*8975f5c5SAndroid Build Coastguard Worker line_columns = line.split(INFO_TAG, 1)[1].split(':') 324*8975f5c5SAndroid Build Coastguard Worker if len(line_columns) is not 3: 325*8975f5c5SAndroid Build Coastguard Worker LOGGER.warning("Line improperly formatted: '" + line + "'\n") 326*8975f5c5SAndroid Build Coastguard Worker continue 327*8975f5c5SAndroid Build Coastguard Worker key = line_columns[1].strip() 328*8975f5c5SAndroid Build Coastguard Worker # If the value is clearly an int, sum it. Otherwise, concatenate it as a string 329*8975f5c5SAndroid Build Coastguard Worker isInt = False 330*8975f5c5SAndroid Build Coastguard Worker intVal = 0 331*8975f5c5SAndroid Build Coastguard Worker try: 332*8975f5c5SAndroid Build Coastguard Worker intVal = int(line_columns[2]) 333*8975f5c5SAndroid Build Coastguard Worker if intVal is not None: 334*8975f5c5SAndroid Build Coastguard Worker isInt = True 335*8975f5c5SAndroid Build Coastguard Worker except Exception as error: 336*8975f5c5SAndroid Build Coastguard Worker isInt = False 337*8975f5c5SAndroid Build Coastguard Worker 338*8975f5c5SAndroid Build Coastguard Worker if isInt: 339*8975f5c5SAndroid Build Coastguard Worker if key not in step_info: 340*8975f5c5SAndroid Build Coastguard Worker step_info[key] = 0 341*8975f5c5SAndroid Build Coastguard Worker step_info[key] += intVal 342*8975f5c5SAndroid Build Coastguard Worker else: 343*8975f5c5SAndroid Build Coastguard Worker if key not in step_info: 344*8975f5c5SAndroid Build Coastguard Worker step_info[key] = line_columns[2].strip() 345*8975f5c5SAndroid Build Coastguard Worker else: 346*8975f5c5SAndroid Build Coastguard Worker append_string = '\n' + line_columns[2].strip() 347*8975f5c5SAndroid Build Coastguard Worker # Sheets has a limit of 50000 characters per cell, so make sure to stop appending 348*8975f5c5SAndroid Build Coastguard Worker # below this limit 349*8975f5c5SAndroid Build Coastguard Worker if len(step_info[key]) + len(append_string) < 50000: 350*8975f5c5SAndroid Build Coastguard Worker step_info[key] += append_string 351*8975f5c5SAndroid Build Coastguard Worker else: 352*8975f5c5SAndroid Build Coastguard Worker if key not in append_errors: 353*8975f5c5SAndroid Build Coastguard Worker append_errors.append(key) 354*8975f5c5SAndroid Build Coastguard Worker LOGGER.warning("Too many characters in column '" + key + 355*8975f5c5SAndroid Build Coastguard Worker "'. Output capped.") 356*8975f5c5SAndroid Build Coastguard Worker return step_info 357*8975f5c5SAndroid Build Coastguard Worker 358*8975f5c5SAndroid Build Coastguard Worker 359*8975f5c5SAndroid Build Coastguard Worker# Returns the info for each step run on a given bot_name. 360*8975f5c5SAndroid Build Coastguard Workerdef get_bot_info(bot_name): 361*8975f5c5SAndroid Build Coastguard Worker info = get_latest_success_build_info(bot_name) 362*8975f5c5SAndroid Build Coastguard Worker info['step_names'] = get_step_names(info['build_name']) 363*8975f5c5SAndroid Build Coastguard Worker broken_step_names = [] 364*8975f5c5SAndroid Build Coastguard Worker for step_name in info['step_names']: 365*8975f5c5SAndroid Build Coastguard Worker LOGGER.info("Parsing step '" + step_name + "'...") 366*8975f5c5SAndroid Build Coastguard Worker step_info = get_step_info(info['build_name'], step_name) 367*8975f5c5SAndroid Build Coastguard Worker if validate_step_info(step_info, info['build_name'], step_name): 368*8975f5c5SAndroid Build Coastguard Worker info[step_name] = step_info 369*8975f5c5SAndroid Build Coastguard Worker else: 370*8975f5c5SAndroid Build Coastguard Worker broken_step_names += step_name 371*8975f5c5SAndroid Build Coastguard Worker for step_name in broken_step_names: 372*8975f5c5SAndroid Build Coastguard Worker info['step_names'].remove(step_name) 373*8975f5c5SAndroid Build Coastguard Worker return info 374*8975f5c5SAndroid Build Coastguard Worker 375*8975f5c5SAndroid Build Coastguard Worker 376*8975f5c5SAndroid Build Coastguard Worker##################### 377*8975f5c5SAndroid Build Coastguard Worker# Sheets Formatting # 378*8975f5c5SAndroid Build Coastguard Worker##################### 379*8975f5c5SAndroid Build Coastguard Worker 380*8975f5c5SAndroid Build Coastguard Worker 381*8975f5c5SAndroid Build Coastguard Worker# Get an individual spreadsheet based on the spreadsheet id. Returns the result of 382*8975f5c5SAndroid Build Coastguard Worker# spreadsheets.get(), or throws an exception if the sheet could not open. 383*8975f5c5SAndroid Build Coastguard Workerdef get_spreadsheet(service, spreadsheet_id): 384*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Called [spreadsheets.get(spreadsheetId='" + spreadsheet_id + "')]") 385*8975f5c5SAndroid Build Coastguard Worker request = service.get(spreadsheetId=spreadsheet_id) 386*8975f5c5SAndroid Build Coastguard Worker spreadsheet = request.execute() 387*8975f5c5SAndroid Build Coastguard Worker if not spreadsheet: 388*8975f5c5SAndroid Build Coastguard Worker raise Exception("Did not open spreadsheet '" + spreadsheet_id + "'") 389*8975f5c5SAndroid Build Coastguard Worker return spreadsheet 390*8975f5c5SAndroid Build Coastguard Worker 391*8975f5c5SAndroid Build Coastguard Worker 392*8975f5c5SAndroid Build Coastguard Worker# Returns a nicely formatted string based on the bot_name and step_name 393*8975f5c5SAndroid Build Coastguard Workerdef format_sheet_name(bot_name, step_name): 394*8975f5c5SAndroid Build Coastguard Worker # Some tokens should be ignored for readability in the name 395*8975f5c5SAndroid Build Coastguard Worker unneccesary_tokens = ['FYI', 'Release', 'Vk', 'dEQP', '(', ')'] 396*8975f5c5SAndroid Build Coastguard Worker for token in unneccesary_tokens: 397*8975f5c5SAndroid Build Coastguard Worker bot_name = bot_name.replace(token, '') 398*8975f5c5SAndroid Build Coastguard Worker bot_name = ' '.join(bot_name.strip().split()) # Remove extra spaces 399*8975f5c5SAndroid Build Coastguard Worker step_name = re.findall(r'angle\w*', step_name)[0] # Separate test name 400*8975f5c5SAndroid Build Coastguard Worker # Test names are formatted as 'angle_deqp_<frontend>_<backend>_tests' 401*8975f5c5SAndroid Build Coastguard Worker new_step_name = '' 402*8975f5c5SAndroid Build Coastguard Worker # Put the frontend first 403*8975f5c5SAndroid Build Coastguard Worker if '_egl_' in step_name: 404*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_egl_', '_') 405*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' EGL' 406*8975f5c5SAndroid Build Coastguard Worker if '_gles2_' in step_name: 407*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_gles2_', '_') 408*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' GLES 2.0 ' 409*8975f5c5SAndroid Build Coastguard Worker if '_gles3_' in step_name: 410*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_gles3_', '_') 411*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' GLES 3.0 ' 412*8975f5c5SAndroid Build Coastguard Worker if '_gles31_' in step_name: 413*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_gles31_', '_') 414*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' GLES 3.1 ' 415*8975f5c5SAndroid Build Coastguard Worker # Put the backend second 416*8975f5c5SAndroid Build Coastguard Worker if '_d3d9_' in step_name: 417*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_d3d9_', '_') 418*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' D3D9 ' 419*8975f5c5SAndroid Build Coastguard Worker if '_d3d11' in step_name: 420*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_d3d11_', '_') 421*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' D3D11 ' 422*8975f5c5SAndroid Build Coastguard Worker if '_gl_' in step_name: 423*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_gl_', '_') 424*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' Desktop OpenGL ' 425*8975f5c5SAndroid Build Coastguard Worker if '_gles_' in step_name: 426*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_gles_', '_') 427*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' OpenGLES ' 428*8975f5c5SAndroid Build Coastguard Worker if '_vulkan_' in step_name: 429*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_vulkan_', '_') 430*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' Vulkan ' 431*8975f5c5SAndroid Build Coastguard Worker # Add any remaining keywords from the step name into the formatted name (formatted nicely) 432*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('angle_', '_') 433*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_deqp_', '_') 434*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_tests', '_') 435*8975f5c5SAndroid Build Coastguard Worker step_name = step_name.replace('_', ' ').strip() 436*8975f5c5SAndroid Build Coastguard Worker new_step_name += ' ' + step_name 437*8975f5c5SAndroid Build Coastguard Worker new_step_name = ' '.join(new_step_name.strip().split()) # Remove extra spaces 438*8975f5c5SAndroid Build Coastguard Worker return new_step_name + ' ' + bot_name 439*8975f5c5SAndroid Build Coastguard Worker 440*8975f5c5SAndroid Build Coastguard Worker 441*8975f5c5SAndroid Build Coastguard Worker# Returns the full list of sheet names that should be populated based on the info struct 442*8975f5c5SAndroid Build Coastguard Workerdef get_sheet_names(info): 443*8975f5c5SAndroid Build Coastguard Worker sheet_names = [] 444*8975f5c5SAndroid Build Coastguard Worker for bot_name in info: 445*8975f5c5SAndroid Build Coastguard Worker for step_name in info[bot_name]['step_names']: 446*8975f5c5SAndroid Build Coastguard Worker sheet_name = format_sheet_name(bot_name, step_name) 447*8975f5c5SAndroid Build Coastguard Worker sheet_names.append(sheet_name) 448*8975f5c5SAndroid Build Coastguard Worker return sheet_names 449*8975f5c5SAndroid Build Coastguard Worker 450*8975f5c5SAndroid Build Coastguard Worker 451*8975f5c5SAndroid Build Coastguard Worker# Returns True if the sheet is found in the spreadsheets object 452*8975f5c5SAndroid Build Coastguard Workerdef sheet_exists(spreadsheet, step_name): 453*8975f5c5SAndroid Build Coastguard Worker for sheet in spreadsheet['sheets']: 454*8975f5c5SAndroid Build Coastguard Worker if sheet['properties']['title'] == step_name: 455*8975f5c5SAndroid Build Coastguard Worker return True 456*8975f5c5SAndroid Build Coastguard Worker return False 457*8975f5c5SAndroid Build Coastguard Worker 458*8975f5c5SAndroid Build Coastguard Worker 459*8975f5c5SAndroid Build Coastguard Worker# Validates the spreadsheets object against the list of sheet names which should appear. Returns a 460*8975f5c5SAndroid Build Coastguard Worker# list of sheets that need creation. 461*8975f5c5SAndroid Build Coastguard Workerdef validate_sheets(spreadsheet, sheet_names): 462*8975f5c5SAndroid Build Coastguard Worker create_sheets = [] 463*8975f5c5SAndroid Build Coastguard Worker for sheet_name in sheet_names: 464*8975f5c5SAndroid Build Coastguard Worker if not sheet_exists(spreadsheet, sheet_name): 465*8975f5c5SAndroid Build Coastguard Worker create_sheets.append(sheet_name) 466*8975f5c5SAndroid Build Coastguard Worker return create_sheets 467*8975f5c5SAndroid Build Coastguard Worker 468*8975f5c5SAndroid Build Coastguard Worker 469*8975f5c5SAndroid Build Coastguard Worker# Performs a batch update with a given service, spreadsheet id, and list <object(Request)> of 470*8975f5c5SAndroid Build Coastguard Worker# updates to do. 471*8975f5c5SAndroid Build Coastguard Workerdef batch_update(service, spreadsheet_id, updates): 472*8975f5c5SAndroid Build Coastguard Worker batch_update_request_body = { 473*8975f5c5SAndroid Build Coastguard Worker 'requests': updates, 474*8975f5c5SAndroid Build Coastguard Worker } 475*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Called [spreadsheets.batchUpdate(spreadsheetId='" + spreadsheet_id + "', body=" + 476*8975f5c5SAndroid Build Coastguard Worker str(batch_update_request_body) + ')]') 477*8975f5c5SAndroid Build Coastguard Worker request = service.batchUpdate(spreadsheetId=spreadsheet_id, body=batch_update_request_body) 478*8975f5c5SAndroid Build Coastguard Worker request.execute() 479*8975f5c5SAndroid Build Coastguard Worker 480*8975f5c5SAndroid Build Coastguard Worker 481*8975f5c5SAndroid Build Coastguard Worker# Creates sheets given a service and spreadsheed id based on a list of sheet names input 482*8975f5c5SAndroid Build Coastguard Workerdef create_sheets(service, spreadsheet_id, sheet_names): 483*8975f5c5SAndroid Build Coastguard Worker updates = [{'addSheet': {'properties': {'title': sheet_name,}}} for sheet_name in sheet_names] 484*8975f5c5SAndroid Build Coastguard Worker batch_update(service, spreadsheet_id, updates) 485*8975f5c5SAndroid Build Coastguard Worker 486*8975f5c5SAndroid Build Coastguard Worker 487*8975f5c5SAndroid Build Coastguard Worker# Calls a values().batchGet() on the service to find the list of column names from each sheet in 488*8975f5c5SAndroid Build Coastguard Worker# sheet_names. Returns a dictionary with one list per sheet_name. 489*8975f5c5SAndroid Build Coastguard Workerdef get_headers(service, spreadsheet_id, sheet_names): 490*8975f5c5SAndroid Build Coastguard Worker header_ranges = [sheet_name + '!A1:Z' for sheet_name in sheet_names] 491*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Called [spreadsheets.values().batchGet(spreadsheetId='" + spreadsheet_id + 492*8975f5c5SAndroid Build Coastguard Worker ', ranges=' + str(header_ranges) + "')]") 493*8975f5c5SAndroid Build Coastguard Worker request = service.values().batchGet(spreadsheetId=spreadsheet_id, ranges=header_ranges) 494*8975f5c5SAndroid Build Coastguard Worker response = request.execute() 495*8975f5c5SAndroid Build Coastguard Worker headers = {} 496*8975f5c5SAndroid Build Coastguard Worker for k, sheet_name in enumerate(sheet_names): 497*8975f5c5SAndroid Build Coastguard Worker if 'values' in response['valueRanges'][k]: 498*8975f5c5SAndroid Build Coastguard Worker # Headers are in the first row of values 499*8975f5c5SAndroid Build Coastguard Worker headers[sheet_name] = response['valueRanges'][k]['values'][0] 500*8975f5c5SAndroid Build Coastguard Worker else: 501*8975f5c5SAndroid Build Coastguard Worker headers[sheet_name] = [] 502*8975f5c5SAndroid Build Coastguard Worker return headers 503*8975f5c5SAndroid Build Coastguard Worker 504*8975f5c5SAndroid Build Coastguard Worker 505*8975f5c5SAndroid Build Coastguard Worker# Calls values().batchUpdate() with supplied list of data <object(ValueRange)> to update on the 506*8975f5c5SAndroid Build Coastguard Worker# service. 507*8975f5c5SAndroid Build Coastguard Workerdef batch_update_values(service, spreadsheet_id, data): 508*8975f5c5SAndroid Build Coastguard Worker batch_update_values_request_body = { 509*8975f5c5SAndroid Build Coastguard Worker 'valueInputOption': 'USER_ENTERED', # Helps with formatting of dates 510*8975f5c5SAndroid Build Coastguard Worker 'data': data, 511*8975f5c5SAndroid Build Coastguard Worker } 512*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Called [spreadsheets.values().batchUpdate(spreadsheetId='" + spreadsheet_id + 513*8975f5c5SAndroid Build Coastguard Worker "', body=" + str(batch_update_values_request_body) + ')]') 514*8975f5c5SAndroid Build Coastguard Worker request = service.values().batchUpdate( 515*8975f5c5SAndroid Build Coastguard Worker spreadsheetId=spreadsheet_id, body=batch_update_values_request_body) 516*8975f5c5SAndroid Build Coastguard Worker request.execute() 517*8975f5c5SAndroid Build Coastguard Worker 518*8975f5c5SAndroid Build Coastguard Worker 519*8975f5c5SAndroid Build Coastguard Worker# Get the sheetId of a sheet based on its name 520*8975f5c5SAndroid Build Coastguard Workerdef get_sheet_id(spreadsheet, sheet_name): 521*8975f5c5SAndroid Build Coastguard Worker for sheet in spreadsheet['sheets']: 522*8975f5c5SAndroid Build Coastguard Worker if sheet['properties']['title'] == sheet_name: 523*8975f5c5SAndroid Build Coastguard Worker return sheet['properties']['sheetId'] 524*8975f5c5SAndroid Build Coastguard Worker return -1 525*8975f5c5SAndroid Build Coastguard Worker 526*8975f5c5SAndroid Build Coastguard Worker 527*8975f5c5SAndroid Build Coastguard Worker# Update the filters on sheets with a 'duplicate' column. Filter out any duplicate rows 528*8975f5c5SAndroid Build Coastguard Workerdef update_filters(service, spreadsheet_id, headers, info, spreadsheet): 529*8975f5c5SAndroid Build Coastguard Worker updates = [] 530*8975f5c5SAndroid Build Coastguard Worker for bot_name in info: 531*8975f5c5SAndroid Build Coastguard Worker for step_name in info[bot_name]['step_names']: 532*8975f5c5SAndroid Build Coastguard Worker sheet_name = format_sheet_name(bot_name, step_name) 533*8975f5c5SAndroid Build Coastguard Worker duplicate_found = 'duplicate' in headers[sheet_name] 534*8975f5c5SAndroid Build Coastguard Worker if duplicate_found: 535*8975f5c5SAndroid Build Coastguard Worker sheet_id = get_sheet_id(spreadsheet, sheet_name) 536*8975f5c5SAndroid Build Coastguard Worker if sheet_id > -1: 537*8975f5c5SAndroid Build Coastguard Worker updates.append({ 538*8975f5c5SAndroid Build Coastguard Worker "setBasicFilter": { 539*8975f5c5SAndroid Build Coastguard Worker "filter": { 540*8975f5c5SAndroid Build Coastguard Worker "range": { 541*8975f5c5SAndroid Build Coastguard Worker "sheetId": sheet_id, 542*8975f5c5SAndroid Build Coastguard Worker "startColumnIndex": 0, 543*8975f5c5SAndroid Build Coastguard Worker "endColumnIndex": len(headers[sheet_name]) 544*8975f5c5SAndroid Build Coastguard Worker }, 545*8975f5c5SAndroid Build Coastguard Worker "sortSpecs": [{ 546*8975f5c5SAndroid Build Coastguard Worker "dimensionIndex": headers[sheet_name].index('date'), 547*8975f5c5SAndroid Build Coastguard Worker "sortOrder": "ASCENDING" 548*8975f5c5SAndroid Build Coastguard Worker }], 549*8975f5c5SAndroid Build Coastguard Worker "criteria": { 550*8975f5c5SAndroid Build Coastguard Worker str(headers[sheet_name].index('duplicate')): { 551*8975f5c5SAndroid Build Coastguard Worker "hiddenValues": 552*8975f5c5SAndroid Build Coastguard Worker ["1"] # Hide rows when duplicate is 1 (true) 553*8975f5c5SAndroid Build Coastguard Worker } 554*8975f5c5SAndroid Build Coastguard Worker } 555*8975f5c5SAndroid Build Coastguard Worker } 556*8975f5c5SAndroid Build Coastguard Worker } 557*8975f5c5SAndroid Build Coastguard Worker }) 558*8975f5c5SAndroid Build Coastguard Worker if updates: 559*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Updating sheet filters...') 560*8975f5c5SAndroid Build Coastguard Worker batch_update(service, spreadsheet_id, updates) 561*8975f5c5SAndroid Build Coastguard Worker 562*8975f5c5SAndroid Build Coastguard Worker# Populates the headers with any missing/desired rows based on the info struct, and calls 563*8975f5c5SAndroid Build Coastguard Worker# batch update to update the corresponding sheets if necessary. 564*8975f5c5SAndroid Build Coastguard Workerdef update_headers(service, spreadsheet_id, headers, info): 565*8975f5c5SAndroid Build Coastguard Worker data = [] 566*8975f5c5SAndroid Build Coastguard Worker sheet_names = [] 567*8975f5c5SAndroid Build Coastguard Worker for bot_name in info: 568*8975f5c5SAndroid Build Coastguard Worker for step_name in info[bot_name]['step_names']: 569*8975f5c5SAndroid Build Coastguard Worker if not step_name in info[bot_name]: 570*8975f5c5SAndroid Build Coastguard Worker LOGGER.error("Missing info for step name: '" + step_name + "'") 571*8975f5c5SAndroid Build Coastguard Worker sheet_name = format_sheet_name(bot_name, step_name) 572*8975f5c5SAndroid Build Coastguard Worker headers_stale = False 573*8975f5c5SAndroid Build Coastguard Worker # Headers should always contain the following columns 574*8975f5c5SAndroid Build Coastguard Worker for req in REQUIRED_COLUMNS: 575*8975f5c5SAndroid Build Coastguard Worker if req not in headers[sheet_name]: 576*8975f5c5SAndroid Build Coastguard Worker headers_stale = True 577*8975f5c5SAndroid Build Coastguard Worker headers[sheet_name].append(req) 578*8975f5c5SAndroid Build Coastguard Worker # Headers also must contain all the keys seen in this step 579*8975f5c5SAndroid Build Coastguard Worker for key in info[bot_name][step_name].keys(): 580*8975f5c5SAndroid Build Coastguard Worker if key not in headers[sheet_name]: 581*8975f5c5SAndroid Build Coastguard Worker headers_stale = True 582*8975f5c5SAndroid Build Coastguard Worker headers[sheet_name].append(key) 583*8975f5c5SAndroid Build Coastguard Worker # Update the Gdoc headers if necessary 584*8975f5c5SAndroid Build Coastguard Worker if headers_stale: 585*8975f5c5SAndroid Build Coastguard Worker sheet_names.append(sheet_name) 586*8975f5c5SAndroid Build Coastguard Worker header_range = sheet_name + '!A1:Z' 587*8975f5c5SAndroid Build Coastguard Worker data.append({ 588*8975f5c5SAndroid Build Coastguard Worker 'range': header_range, 589*8975f5c5SAndroid Build Coastguard Worker 'majorDimension': 'ROWS', 590*8975f5c5SAndroid Build Coastguard Worker 'values': [headers[sheet_name]] 591*8975f5c5SAndroid Build Coastguard Worker }) 592*8975f5c5SAndroid Build Coastguard Worker if data: 593*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Updating sheet headers...') 594*8975f5c5SAndroid Build Coastguard Worker batch_update_values(service, spreadsheet_id, data) 595*8975f5c5SAndroid Build Coastguard Worker 596*8975f5c5SAndroid Build Coastguard Worker# Calls values().append() to append a list of values to a given sheet. 597*8975f5c5SAndroid Build Coastguard Workerdef append_values(service, spreadsheet_id, sheet_name, values): 598*8975f5c5SAndroid Build Coastguard Worker header_range = sheet_name + '!A1:Z' 599*8975f5c5SAndroid Build Coastguard Worker insert_data_option = 'INSERT_ROWS' 600*8975f5c5SAndroid Build Coastguard Worker value_input_option = 'USER_ENTERED' # Helps with formatting of dates 601*8975f5c5SAndroid Build Coastguard Worker append_values_request_body = { 602*8975f5c5SAndroid Build Coastguard Worker 'range': header_range, 603*8975f5c5SAndroid Build Coastguard Worker 'majorDimension': 'ROWS', 604*8975f5c5SAndroid Build Coastguard Worker 'values': [values], 605*8975f5c5SAndroid Build Coastguard Worker } 606*8975f5c5SAndroid Build Coastguard Worker LOGGER.debug("Called [spreadsheets.values().append(spreadsheetId='" + spreadsheet_id + 607*8975f5c5SAndroid Build Coastguard Worker "', body=" + str(append_values_request_body) + ", range='" + header_range + 608*8975f5c5SAndroid Build Coastguard Worker "', insertDataOption='" + insert_data_option + "', valueInputOption='" + 609*8975f5c5SAndroid Build Coastguard Worker value_input_option + "')]") 610*8975f5c5SAndroid Build Coastguard Worker request = service.values().append( 611*8975f5c5SAndroid Build Coastguard Worker spreadsheetId=spreadsheet_id, 612*8975f5c5SAndroid Build Coastguard Worker body=append_values_request_body, 613*8975f5c5SAndroid Build Coastguard Worker range=header_range, 614*8975f5c5SAndroid Build Coastguard Worker insertDataOption=insert_data_option, 615*8975f5c5SAndroid Build Coastguard Worker valueInputOption=value_input_option) 616*8975f5c5SAndroid Build Coastguard Worker request.execute() 617*8975f5c5SAndroid Build Coastguard Worker 618*8975f5c5SAndroid Build Coastguard Worker 619*8975f5c5SAndroid Build Coastguard Worker# Formula to determine whether a row is a duplicate of the previous row based on checking the 620*8975f5c5SAndroid Build Coastguard Worker# columns listed in filter_columns. 621*8975f5c5SAndroid Build Coastguard Worker# Eg. 622*8975f5c5SAndroid Build Coastguard Worker# date | pass | fail 623*8975f5c5SAndroid Build Coastguard Worker# Jan 1 100 50 624*8975f5c5SAndroid Build Coastguard Worker# Jan 2 100 50 625*8975f5c5SAndroid Build Coastguard Worker# Jan 3 99 51 626*8975f5c5SAndroid Build Coastguard Worker# 627*8975f5c5SAndroid Build Coastguard Worker# If we want to filter based on only the "pass" and "fail" columns, we generate the following 628*8975f5c5SAndroid Build Coastguard Worker# formula in the 'duplicate' column: 'IF(B1=B0, IF(C1=C0,1,0) ,0); 629*8975f5c5SAndroid Build Coastguard Worker# This formula is recursively generated for each column in filter_columns, using the column 630*8975f5c5SAndroid Build Coastguard Worker# position as determined by headers. The formula uses a more generalized form with 631*8975f5c5SAndroid Build Coastguard Worker# 'INDIRECT(ADDRESS(<row>, <col>))'' instead of 'B1', where <row> is Row() and Row()-1, and col is 632*8975f5c5SAndroid Build Coastguard Worker# determined by the column's position in headers 633*8975f5c5SAndroid Build Coastguard Workerdef generate_duplicate_formula(headers, filter_columns): 634*8975f5c5SAndroid Build Coastguard Worker # No more columns, put a 1 in the IF statement true branch 635*8975f5c5SAndroid Build Coastguard Worker if len(filter_columns) == 0: 636*8975f5c5SAndroid Build Coastguard Worker return '1' 637*8975f5c5SAndroid Build Coastguard Worker # Next column is found, generate the formula for duplicate checking, and remove from the list 638*8975f5c5SAndroid Build Coastguard Worker # for recursion 639*8975f5c5SAndroid Build Coastguard Worker for i in range(len(headers)): 640*8975f5c5SAndroid Build Coastguard Worker if headers[i] == filter_columns[0]: 641*8975f5c5SAndroid Build Coastguard Worker col = str(i + 1) 642*8975f5c5SAndroid Build Coastguard Worker formula = "IF(INDIRECT(ADDRESS(ROW(), " + col + "))=INDIRECT(ADDRESS(ROW() - 1, " + \ 643*8975f5c5SAndroid Build Coastguard Worker col + "))," + generate_duplicate_formula(headers, filter_columns[1:]) + ",0)" 644*8975f5c5SAndroid Build Coastguard Worker return formula 645*8975f5c5SAndroid Build Coastguard Worker # Next column not found, remove from recursion but just return whatever the next one is 646*8975f5c5SAndroid Build Coastguard Worker return generate_duplicate_formula(headers, filter_columns[1:]) 647*8975f5c5SAndroid Build Coastguard Worker 648*8975f5c5SAndroid Build Coastguard Worker 649*8975f5c5SAndroid Build Coastguard Worker# Helper function to start the recursive call to generate_duplicate_formula 650*8975f5c5SAndroid Build Coastguard Workerdef generate_duplicate_formula_helper(headers): 651*8975f5c5SAndroid Build Coastguard Worker filter_columns = MAIN_RESULT_COLUMNS 652*8975f5c5SAndroid Build Coastguard Worker formula = generate_duplicate_formula(headers, filter_columns) 653*8975f5c5SAndroid Build Coastguard Worker if (formula == "1"): 654*8975f5c5SAndroid Build Coastguard Worker return "" 655*8975f5c5SAndroid Build Coastguard Worker else: 656*8975f5c5SAndroid Build Coastguard Worker # Final result needs to be prepended with = 657*8975f5c5SAndroid Build Coastguard Worker return "=" + formula 658*8975f5c5SAndroid Build Coastguard Worker 659*8975f5c5SAndroid Build Coastguard Worker# Uses the list of headers and the info struct to come up with a list of values for each step 660*8975f5c5SAndroid Build Coastguard Worker# from the latest builds. 661*8975f5c5SAndroid Build Coastguard Workerdef update_values(service, spreadsheet_id, headers, info): 662*8975f5c5SAndroid Build Coastguard Worker data = [] 663*8975f5c5SAndroid Build Coastguard Worker for bot_name in info: 664*8975f5c5SAndroid Build Coastguard Worker for step_name in info[bot_name]['step_names']: 665*8975f5c5SAndroid Build Coastguard Worker sheet_name = format_sheet_name(bot_name, step_name) 666*8975f5c5SAndroid Build Coastguard Worker values = [] 667*8975f5c5SAndroid Build Coastguard Worker # For each key in the list of headers, either add the corresponding value or add a blank 668*8975f5c5SAndroid Build Coastguard Worker # value. It's necessary for the values to match the order of the headers 669*8975f5c5SAndroid Build Coastguard Worker for key in headers[sheet_name]: 670*8975f5c5SAndroid Build Coastguard Worker if key in info[bot_name] and key in REQUIRED_COLUMNS: 671*8975f5c5SAndroid Build Coastguard Worker values.append(info[bot_name][key]) 672*8975f5c5SAndroid Build Coastguard Worker elif key in info[bot_name][step_name]: 673*8975f5c5SAndroid Build Coastguard Worker values.append(info[bot_name][step_name][key]) 674*8975f5c5SAndroid Build Coastguard Worker elif key == "duplicate" and key in REQUIRED_COLUMNS: 675*8975f5c5SAndroid Build Coastguard Worker values.append(generate_duplicate_formula_helper(headers[sheet_name])) 676*8975f5c5SAndroid Build Coastguard Worker else: 677*8975f5c5SAndroid Build Coastguard Worker values.append('') 678*8975f5c5SAndroid Build Coastguard Worker LOGGER.info("Appending new rows to sheet '" + sheet_name + "'...") 679*8975f5c5SAndroid Build Coastguard Worker try: 680*8975f5c5SAndroid Build Coastguard Worker append_values(service, spreadsheet_id, sheet_name, values) 681*8975f5c5SAndroid Build Coastguard Worker except Exception as error: 682*8975f5c5SAndroid Build Coastguard Worker LOGGER.warning('%s\n' % str(error)) 683*8975f5c5SAndroid Build Coastguard Worker 684*8975f5c5SAndroid Build Coastguard Worker 685*8975f5c5SAndroid Build Coastguard Worker# Updates the given spreadsheed_id with the info struct passed in. 686*8975f5c5SAndroid Build Coastguard Workerdef update_spreadsheet(service, spreadsheet_id, info): 687*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Opening spreadsheet...') 688*8975f5c5SAndroid Build Coastguard Worker spreadsheet = get_spreadsheet(service, spreadsheet_id) 689*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Parsing sheet names...') 690*8975f5c5SAndroid Build Coastguard Worker sheet_names = get_sheet_names(info) 691*8975f5c5SAndroid Build Coastguard Worker new_sheets = validate_sheets(spreadsheet, sheet_names) 692*8975f5c5SAndroid Build Coastguard Worker if new_sheets: 693*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Creating new sheets...') 694*8975f5c5SAndroid Build Coastguard Worker create_sheets(service, spreadsheet_id, new_sheets) 695*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Parsing sheet headers...') 696*8975f5c5SAndroid Build Coastguard Worker headers = get_headers(service, spreadsheet_id, sheet_names) 697*8975f5c5SAndroid Build Coastguard Worker update_headers(service, spreadsheet_id, headers, info) 698*8975f5c5SAndroid Build Coastguard Worker update_filters(service, spreadsheet_id, headers, info, spreadsheet) 699*8975f5c5SAndroid Build Coastguard Worker update_values(service, spreadsheet_id, headers, info) 700*8975f5c5SAndroid Build Coastguard Worker 701*8975f5c5SAndroid Build Coastguard Worker 702*8975f5c5SAndroid Build Coastguard Worker##################### 703*8975f5c5SAndroid Build Coastguard Worker# Main/helpers # 704*8975f5c5SAndroid Build Coastguard Worker##################### 705*8975f5c5SAndroid Build Coastguard Worker 706*8975f5c5SAndroid Build Coastguard Worker 707*8975f5c5SAndroid Build Coastguard Worker# Loads or creates credentials and connects to the Sheets API. Returns a Spreadsheets object with 708*8975f5c5SAndroid Build Coastguard Worker# an open connection. 709*8975f5c5SAndroid Build Coastguard Workerdef get_sheets_service(auth_path): 710*8975f5c5SAndroid Build Coastguard Worker credentials_path = auth_path + '/credentials.json' 711*8975f5c5SAndroid Build Coastguard Worker token_path = auth_path + '/token.pickle' 712*8975f5c5SAndroid Build Coastguard Worker creds = None 713*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(auth_path): 714*8975f5c5SAndroid Build Coastguard Worker LOGGER.info("Creating auth dir '" + auth_path + "'") 715*8975f5c5SAndroid Build Coastguard Worker os.makedirs(auth_path) 716*8975f5c5SAndroid Build Coastguard Worker if not os.path.exists(credentials_path): 717*8975f5c5SAndroid Build Coastguard Worker raise Exception('Missing credentials.json.\n' 718*8975f5c5SAndroid Build Coastguard Worker 'Go to: https://developers.google.com/sheets/api/quickstart/python\n' 719*8975f5c5SAndroid Build Coastguard Worker "Under Step 1, click 'ENABLE THE GOOGLE SHEETS API'\n" 720*8975f5c5SAndroid Build Coastguard Worker "Click 'DOWNLOAD CLIENT CONFIGURATION'\n" 721*8975f5c5SAndroid Build Coastguard Worker 'Save to your auth_path (' + auth_path + ') as credentials.json') 722*8975f5c5SAndroid Build Coastguard Worker if os.path.exists(token_path): 723*8975f5c5SAndroid Build Coastguard Worker with open(token_path, 'rb') as token: 724*8975f5c5SAndroid Build Coastguard Worker creds = pickle.load(token) 725*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Loaded credentials from ' + token_path) 726*8975f5c5SAndroid Build Coastguard Worker if not creds or not creds.valid: 727*8975f5c5SAndroid Build Coastguard Worker if creds and creds.expired and creds.refresh_token: 728*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Refreshing credentials...') 729*8975f5c5SAndroid Build Coastguard Worker creds.refresh(Request()) 730*8975f5c5SAndroid Build Coastguard Worker else: 731*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Could not find credentials. Requesting new credentials.') 732*8975f5c5SAndroid Build Coastguard Worker flow = InstalledAppFlow.from_client_secrets_file(credentials_path, SCOPES) 733*8975f5c5SAndroid Build Coastguard Worker creds = flow.run_local_server() 734*8975f5c5SAndroid Build Coastguard Worker with open(token_path, 'wb') as token: 735*8975f5c5SAndroid Build Coastguard Worker pickle.dump(creds, token) 736*8975f5c5SAndroid Build Coastguard Worker service = build('sheets', 'v4', credentials=creds) 737*8975f5c5SAndroid Build Coastguard Worker sheets = service.spreadsheets() 738*8975f5c5SAndroid Build Coastguard Worker return sheets 739*8975f5c5SAndroid Build Coastguard Worker 740*8975f5c5SAndroid Build Coastguard Worker 741*8975f5c5SAndroid Build Coastguard Worker# Parse the input to the script 742*8975f5c5SAndroid Build Coastguard Workerdef parse_args(): 743*8975f5c5SAndroid Build Coastguard Worker parser = argparse.ArgumentParser(os.path.basename(sys.argv[0])) 744*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 745*8975f5c5SAndroid Build Coastguard Worker '--auth_path', 746*8975f5c5SAndroid Build Coastguard Worker default=HOME_DIR + '/.auth', 747*8975f5c5SAndroid Build Coastguard Worker nargs='?', 748*8975f5c5SAndroid Build Coastguard Worker help='path to directory containing authorization data ' 749*8975f5c5SAndroid Build Coastguard Worker '(credentials.json and token.pickle). ' 750*8975f5c5SAndroid Build Coastguard Worker '[default=<home>/.auth]') 751*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 752*8975f5c5SAndroid Build Coastguard Worker '--spreadsheet', 753*8975f5c5SAndroid Build Coastguard Worker default='1uttk1z8lJ4ZsUY7wMdFauMzUxb048nh5l52zdrAznek', 754*8975f5c5SAndroid Build Coastguard Worker nargs='?', 755*8975f5c5SAndroid Build Coastguard Worker help='ID of the spreadsheet to write stats to. ' 756*8975f5c5SAndroid Build Coastguard Worker "[default='1uttk1z8lJ4ZsUY7wMdFauMzUxb048nh5l52zdrAznek']") 757*8975f5c5SAndroid Build Coastguard Worker parser.add_argument( 758*8975f5c5SAndroid Build Coastguard Worker '--verbosity', 759*8975f5c5SAndroid Build Coastguard Worker default='INFO', 760*8975f5c5SAndroid Build Coastguard Worker nargs='?', 761*8975f5c5SAndroid Build Coastguard Worker help='Verbosity of output. Valid options are ' 762*8975f5c5SAndroid Build Coastguard Worker '[DEBUG, INFO, WARNING, ERROR]. ' 763*8975f5c5SAndroid Build Coastguard Worker '[default=INFO]') 764*8975f5c5SAndroid Build Coastguard Worker return parser.parse_args() 765*8975f5c5SAndroid Build Coastguard Worker 766*8975f5c5SAndroid Build Coastguard Worker 767*8975f5c5SAndroid Build Coastguard Worker# Set up the logging with the right verbosity and output. 768*8975f5c5SAndroid Build Coastguard Workerdef initialize_logging(verbosity): 769*8975f5c5SAndroid Build Coastguard Worker handler = logging.StreamHandler() 770*8975f5c5SAndroid Build Coastguard Worker formatter = logging.Formatter(fmt='%(levelname)s: %(message)s') 771*8975f5c5SAndroid Build Coastguard Worker handler.setFormatter(formatter) 772*8975f5c5SAndroid Build Coastguard Worker LOGGER.addHandler(handler) 773*8975f5c5SAndroid Build Coastguard Worker if 'DEBUG' in verbosity: 774*8975f5c5SAndroid Build Coastguard Worker LOGGER.setLevel(level=logging.DEBUG) 775*8975f5c5SAndroid Build Coastguard Worker elif 'INFO' in verbosity: 776*8975f5c5SAndroid Build Coastguard Worker LOGGER.setLevel(level=logging.INFO) 777*8975f5c5SAndroid Build Coastguard Worker elif 'WARNING' in verbosity: 778*8975f5c5SAndroid Build Coastguard Worker LOGGER.setLevel(level=logging.WARNING) 779*8975f5c5SAndroid Build Coastguard Worker elif 'ERROR' in verbosity: 780*8975f5c5SAndroid Build Coastguard Worker LOGGER.setLevel(level=logging.ERROR) 781*8975f5c5SAndroid Build Coastguard Worker else: 782*8975f5c5SAndroid Build Coastguard Worker LOGGER.setLevel(level=logging.INFO) 783*8975f5c5SAndroid Build Coastguard Worker 784*8975f5c5SAndroid Build Coastguard Worker 785*8975f5c5SAndroid Build Coastguard Workerdef main(): 786*8975f5c5SAndroid Build Coastguard Worker os.chdir(ROOT_DIR) 787*8975f5c5SAndroid Build Coastguard Worker args = parse_args() 788*8975f5c5SAndroid Build Coastguard Worker verbosity = args.verbosity.strip().upper() 789*8975f5c5SAndroid Build Coastguard Worker initialize_logging(verbosity) 790*8975f5c5SAndroid Build Coastguard Worker auth_path = args.auth_path.replace('\\', '/') 791*8975f5c5SAndroid Build Coastguard Worker try: 792*8975f5c5SAndroid Build Coastguard Worker service = get_sheets_service(auth_path) 793*8975f5c5SAndroid Build Coastguard Worker except Exception as error: 794*8975f5c5SAndroid Build Coastguard Worker LOGGER.error('%s\n' % str(error)) 795*8975f5c5SAndroid Build Coastguard Worker exit(1) 796*8975f5c5SAndroid Build Coastguard Worker 797*8975f5c5SAndroid Build Coastguard Worker info = {} 798*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Building info struct...') 799*8975f5c5SAndroid Build Coastguard Worker for bot_name in BOT_NAMES: 800*8975f5c5SAndroid Build Coastguard Worker LOGGER.info("Parsing bot '" + bot_name + "'...") 801*8975f5c5SAndroid Build Coastguard Worker try: 802*8975f5c5SAndroid Build Coastguard Worker info[bot_name] = get_bot_info(BOT_NAME_PREFIX + bot_name) 803*8975f5c5SAndroid Build Coastguard Worker except Exception as error: 804*8975f5c5SAndroid Build Coastguard Worker LOGGER.error('%s\n' % str(error)) 805*8975f5c5SAndroid Build Coastguard Worker 806*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Updating sheets...') 807*8975f5c5SAndroid Build Coastguard Worker try: 808*8975f5c5SAndroid Build Coastguard Worker update_spreadsheet(service, args.spreadsheet, info) 809*8975f5c5SAndroid Build Coastguard Worker except Exception as error: 810*8975f5c5SAndroid Build Coastguard Worker LOGGER.error('%s\n' % str(error)) 811*8975f5c5SAndroid Build Coastguard Worker quit(1) 812*8975f5c5SAndroid Build Coastguard Worker 813*8975f5c5SAndroid Build Coastguard Worker LOGGER.info('Info was successfully parsed to sheet: https://docs.google.com/spreadsheets/d/' + 814*8975f5c5SAndroid Build Coastguard Worker args.spreadsheet) 815*8975f5c5SAndroid Build Coastguard Worker 816*8975f5c5SAndroid Build Coastguard Worker 817*8975f5c5SAndroid Build Coastguard Workerif __name__ == '__main__': 818*8975f5c5SAndroid Build Coastguard Worker sys.exit(main()) 819