| #!/usr/bin/env python3 | |
| # Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
| # for details. All rights reserved. Use of this source code is governed by a | |
| # BSD-style license that can be found in the LICENSE file. | |
| # | |
| """Tool to automatically update the DartFuzzStats spreadsheet | |
| Requires a one-time authentication step with a @google account. | |
| """ | |
| from __future__ import print_function | |
| import pickle | |
| import os.path | |
| import subprocess | |
| from googleapiclient.discovery import build | |
| from google_auth_oauthlib.flow import InstalledAppFlow | |
| from google.auth.transport.requests import Request | |
| # This script may require a one time install of Google API libraries: | |
| # pip3 install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib | |
| # If modifying these scopes, delete the file token.pickle. | |
| SCOPES = ['https://www.googleapis.com/auth/spreadsheets'] | |
| # The ID and range of a spreadsheet. | |
| SPREADSHEET_ID = '1nDoK-dCuEmf6yo55a303UClRd7AwjbzPkRr37ijWcC8' | |
| RANGE_NAME = 'Sheet1!A3:H' | |
| VERIFY_CURRENT_ROW_FORMULA = '=B:B-C:C-D:D-E:E-F:F' | |
| def authenticate(): | |
| dir_path = os.path.dirname(os.path.realpath(__file__)) | |
| creds = None | |
| # The file token.pickle stores the user's access and refresh tokens, and is | |
| # created automatically when the authorization flow completes for the first | |
| # time. | |
| pickle_path = os.path.join(dir_path, 'token.pickle') | |
| if os.path.exists(pickle_path): | |
| with open(pickle_path, 'rb') as token: | |
| creds = pickle.load(token) | |
| # If there are no (valid) credentials available, let the user log in. | |
| if not creds or not creds.valid: | |
| if creds and creds.expired and creds.refresh_token: | |
| creds.refresh(Request()) | |
| else: | |
| flow = InstalledAppFlow.from_client_secrets_file( | |
| os.path.join(dir_path, 'credentials.json'), SCOPES) | |
| creds = flow.run_local_server(port=0) | |
| # Save the credentials for the next run | |
| with open(pickle_path, 'wb') as token: | |
| pickle.dump(creds, token) | |
| return build('sheets', 'v4', credentials=creds) | |
| # Returns the next run ID based on the last run ID found in the fuzzing | |
| # spreadsheet. | |
| def get_next_run_id(sheet): | |
| result = sheet.values().get( | |
| spreadsheetId=SPREADSHEET_ID, range=RANGE_NAME).execute() | |
| values = result.get('values', []) | |
| return int(values[-1][0]) + 1 | |
| # Inserts a new entry into the fuzzing spreadsheet. | |
| def add_new_fuzzing_entry(sheet, run, tests, success, rerun, skipped, timeout, | |
| divergences): | |
| entry = [run, tests, success, skipped, timeout, divergences, rerun] | |
| print( | |
| 'Adding entry for run %d. Tests: %d Successes: %d Skipped: %d Timeouts: %d, Divergences: %d Re-runs: %d' | |
| % tuple(entry)) | |
| values = {'values': [entry + [VERIFY_CURRENT_ROW_FORMULA]]} | |
| sheet.values().append( | |
| spreadsheetId=SPREADSHEET_ID, | |
| range=RANGE_NAME, | |
| body=values, | |
| valueInputOption='USER_ENTERED').execute() | |
| # Scrapes the fuzzing shards for fuzzing run statistics. | |
| # | |
| # Returns a list of statistics in the following order: | |
| # | |
| # - # of tests | |
| # - # of successes | |
| # - # of re-runs | |
| # - # of skipped runs | |
| # - # of timeouts | |
| # - # of divergences | |
| # | |
| def get_run_statistic_summary(run): | |
| dir_path = os.path.dirname(os.path.realpath(__file__)) | |
| output = subprocess.check_output([ | |
| 'python3', | |
| os.path.join(dir_path, 'collect_data.py'), '--output-csv', '--type=sum', | |
| 'https://ci.chromium.org/p/dart/builders/ci.sandbox/fuzz-linux/%d' % run | |
| ]) | |
| return list(map(int, output.decode('UTF-8').rstrip().split(','))) | |
| def main(): | |
| service = authenticate() | |
| # Call the Sheets API | |
| sheet = service.spreadsheets() | |
| while True: | |
| try: | |
| next_id = get_next_run_id(sheet) | |
| summary = get_run_statistic_summary(next_id) | |
| add_new_fuzzing_entry(sheet, next_id, *summary) | |
| except: | |
| # get_run_statistic_summary exits with non-zero exit code if we're out | |
| # of runs to check. | |
| print('No more runs to process. Exiting.') | |
| break | |
| if __name__ == '__main__': | |
| main() |