| #!/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() |