scripts/release/validate_release.py (131 lines of code) (raw):

#!/bin/python3 # Copyright 2019 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from absl import flags import collections import fnmatch import json import logging import os from prettytable import PrettyTable import sys from datetime import date COMMIT = 'headCommitHash' DATE = 'date' RUN_ID = 'runId' STATUS = 'scriptStatus' TEST_ID = 'testId' # List of test that are expected to have run successfully at least once. RUN_TESTS = [ 'long-run-test_gke-grpc-echo', 'long-run-test_gke-grpc-interop', 'long-run-test_gke-http-bookstore', ] FLAGS = flags.FLAGS flags.DEFINE_string( 'commit_sha', '', 'The expected git commit sha' ) flags.DEFINE_string( 'path', '', 'Path where all release information has been extracted' ) flags.DEFINE_boolean( 'detail', False, 'Prints detailed output.' ) class Error(Exception): """Base Error for this class""" class JsonParsingError(Error): """Error for Json parsing.""" class CommitError(Error): """Commit found is not what was expected.""" def findFiles(path, pattern): """Find file in a directory with a given pattern.""" for root, dirs, files in os.walk(path): for file in files: if fnmatch.fnmatch(file, pattern): yield os.path.join(root, file) class ReleaseValidation(object): """Check Release.""" def __init__(self, reference_commit): self._ref_commit = reference_commit self._test_info = collections.defaultdict(dict) self._successful_test = set() def AddJsonData(self, json_file): """Parse Json file and add test info.""" logging.info('Adding information from %s' % json_file) try: with open(json_file) as log: result = json.load(log) except: raise JsonParsingError('Unable to parse %s' % json_file) status = result.get(STATUS, '') test_id = result.get(TEST_ID, '') run_id = result.get(RUN_ID, '') commit = result.get(COMMIT, '') unix_timestamp = result.get(DATE, '') if '' in [commit, run_id, status, test_id]: raise JsonParsingError('Unable to parse %s' % json_file) if commit != self._ref_commit: raise CommitError('%s != %s' % (commit, self._ref_commit)) self._test_info[test_id][run_id] = { DATE: date.fromtimestamp(float(unix_timestamp)), STATUS: int(status) } if int(status) == 0: self._successful_test.add(test_id) def ExtractTestInfoFromPath(self, path): """Extracts test information for a path.""" for json_file in findFiles(path, '*.json'): try: self.AddJsonData(json_file) except Error: logging.error('Could not parse %s', json_file) def PrintAllTests(self): """Prints all test information.""" if not self._test_info: return table = PrettyTable( [TEST_ID, RUN_ID, DATE, STATUS]) for test_id, run in self._test_info.items(): for run_id, values in run.items(): date = values[DATE] status = values[STATUS] table.add_row([test_id, run_id, date, status]) table.align = 'l' table.sortby = TEST_ID print(table) def PrintSummary(self): """Prints tests summary.""" if not self._test_info: return table = PrettyTable( [TEST_ID, 'Success', 'Failure']) for test_id, run in self._test_info.items(): failures, successes = 0, 0 for values in run.values(): status = values[STATUS] if status == 0: successes += 1 else: failures += 1 table.add_row([test_id, successes, failures]) table.align = 'l' table.sortby = TEST_ID print(table) def ValidateRelease(self): missing_tests = set(RUN_TESTS).difference(self._successful_test) if missing_tests: logging.error( 'The following tests haven\'t been run successfully once: \n - %s', '\n - '.join(sorted(missing_tests))) return len(missing_tests) def main(unused_argv): releaseVal = ReleaseValidation(FLAGS.commit_sha) releaseVal.ExtractTestInfoFromPath(FLAGS.path) if FLAGS.detail: releaseVal.PrintAllTests() else: releaseVal.PrintSummary() sys.exit(releaseVal.ValidateRelease()) if __name__ == '__main__': logging.basicConfig(stream=sys.stdout, level=logging.ERROR) try: argv = FLAGS(sys.argv) # Parse flags except: sys.exit('%s\nUsage: %s ARGS\n%s' % (e, sys.argv[0], FLAGS)) if not FLAGS.commit_sha: sys.exit('Flag commit_sha is required') if not FLAGS.path: sys.exit('Flag path required.') main(argv)