From 58582262e483c0909f98f429225cb2498d086143 Mon Sep 17 00:00:00 2001 From: Manoj Gupta Date: Wed, 1 Apr 2020 18:06:07 -0700 Subject: llvm_tools: Add tool for CQ dry run for update CL Add tool to update packages and start a CQ dry run on the CL and its dependencies. BUG=chromium:1067029 TEST=unit tests Change-Id: I593b7ee40985d9146f9d16f3e3e7d64bdeb8727a Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/2133242 Tested-by: Manoj Gupta Reviewed-by: George Burgess --- llvm_tools/update_packages_and_test_cq.py | 196 +++++++++++++++++++++ llvm_tools/update_packages_and_test_cq_unittest.py | 130 ++++++++++++++ 2 files changed, 326 insertions(+) create mode 100755 llvm_tools/update_packages_and_test_cq.py create mode 100755 llvm_tools/update_packages_and_test_cq_unittest.py (limited to 'llvm_tools') diff --git a/llvm_tools/update_packages_and_test_cq.py b/llvm_tools/update_packages_and_test_cq.py new file mode 100755 index 00000000..0e5e409b --- /dev/null +++ b/llvm_tools/update_packages_and_test_cq.py @@ -0,0 +1,196 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Runs CQ dry run after updating the packages.""" + +from __future__ import print_function + +import argparse +import datetime +import json +import os + +from assert_not_in_chroot import VerifyOutsideChroot +from failure_modes import FailureModes +from get_llvm_hash import GetLLVMHashAndVersionFromSVNOption +from get_llvm_hash import is_svn_option +from subprocess_helpers import ChrootRunCommand +from subprocess_helpers import ExecCommandAndCaptureOutput +import update_chromeos_llvm_hash + + +def GetCommandLineArgs(): + """Parses the command line for the command line arguments. + + Returns: + The log level to use when retrieving the LLVM hash or google3 LLVM version, + the chroot path to use for executing chroot commands, + a list of a package or packages to update their LLVM next hash, + and the LLVM version to use when retrieving the LLVM hash. + """ + + # Default path to the chroot if a path is not specified. + cros_root = os.path.expanduser('~') + cros_root = os.path.join(cros_root, 'chromiumos') + + # Create parser and add optional command-line arguments. + parser = argparse.ArgumentParser( + description= + 'Runs a tryjob if successfully updated LLVM_NEXT_HASH of packages.') + + # Add argument for the absolute path to the file that contains information on + # the previous tested svn version. + parser.add_argument( + '--last_tested', + help='the absolute path to the file that contains the last tested ' + 'svn version') + + # Add argument for other change lists that want to run alongside the tryjob + # which has a change list of updating a package's git hash. + parser.add_argument( + '--extra_change_lists', + type=int, + nargs='+', + help='change lists that would like to be run alongside the change list ' + 'of updating the packages') + + # Add argument for a specific chroot path. + parser.add_argument( + '--chroot_path', + default=cros_root, + help='the path to the chroot (default: %(default)s)') + + # Add argument for whether to display command contents to `stdout`. + parser.add_argument( + '--verbose', + action='store_true', + help='display contents of a command to the terminal ' + '(default: %(default)s)') + + # Add argument for the LLVM version to use. + parser.add_argument( + '--llvm_version', + type=is_svn_option, + required=True, + help='which git hash of LLVM to find ' + '{google3, ToT, } ' + '(default: finds the git hash of the google3 LLVM ' + 'version)') + + args_output = parser.parse_args() + + return args_output + + +def GetLastTestedSVNVersion(last_tested_file): + """Gets the lasted tested svn version from the file. + + Args: + last_tested_file: The absolute path to the file that contains the last + tested svn version. + + Returns: + The last tested svn version or 'None' if the file did not have a last tested + svn version (the file exists, but failed to convert the contents to an + integer) or the file does not exist. + """ + + if not last_tested_file: + return None + + last_svn_version = None + + # Get the last tested svn version if the file exists. + try: + with open(last_tested_file) as file_obj: + # For now, the first line contains the last tested svn version. + return int(file_obj.read().rstrip()) + + except (IOError, ValueError): + pass + + return last_svn_version + + +def GetCQDependString(dependent_cls): + """Get CQ dependency string e.g. `Cq-Depend: chromium:MM, chromium:NN`.""" + + if not dependent_cls: + return None + + # Cq-Depend must start a new paragraph prefixed with "Cq-Depend". + return '\nCq-Depend: ' + ', '.join(('chromium:%s' % i) for i in dependent_cls) + + +def startCQDryRun(cl, dependent_cls, chroot_path): + """Start CQ dry run for the changelist and dependencies.""" + + gerrit_abs_path = os.path.join(chroot_path, 'chromite/bin/gerrit') + + cl_list = [cl] + cl_list.extend(dependent_cls) + + for changes in cl_list: + cq_dry_run_cmd = [gerrit_abs_path, 'label-cq', str(changes), '1'] + + ExecCommandAndCaptureOutput(cq_dry_run_cmd) + + +def main(): + """Updates the packages' 'LLVM_NEXT_HASH' and submits tryjobs. + + Raises: + AssertionError: The script was run inside the chroot. + """ + + VerifyOutsideChroot() + + args_output = GetCommandLineArgs() + + last_svn_version = GetLastTestedSVNVersion(args_output.last_tested) + + update_packages = [ + 'sys-devel/llvm', 'sys-libs/compiler-rt', 'sys-libs/libcxx', + 'sys-libs/libcxxabi', 'sys-libs/llvm-libunwind' + ] + + patch_metadata_file = 'PATCHES.json' + + svn_option = args_output.llvm_version + + git_hash, svn_version = GetLLVMHashAndVersionFromSVNOption(svn_option) + + # There is no need to run tryjobs when the SVN version matches the last tested + # SVN version. + if last_svn_version == svn_version: + print('svn version (%d) matches the last tested svn version (%d) in %s' % + (svn_version, last_svn_version, args_output.last_tested)) + return + + update_chromeos_llvm_hash.verbose = args_output.verbose + extra_commit_msg = GetCQDependString(args_output.extra_change_lists) + + change_list = update_chromeos_llvm_hash.UpdatePackages( + update_packages, update_chromeos_llvm_hash.LLVMVariant.next, git_hash, + svn_version, args_output.chroot_path, patch_metadata_file, + FailureModes.DISABLE_PATCHES, svn_option, extra_commit_msg) + + print('Successfully updated packages to %d' % svn_version) + print('Gerrit URL: %s' % change_list.url) + print('Change list number: %d' % change_list.cl_number) + + startCQDryRun(change_list.cl_number, args_output.extra_change_lists, + args_output.chroot_path) + + # Updated the packages and submitted tryjobs successfully, so the file will + # contain 'svn_version' which will now become the last tested svn version. + if args_output.last_tested: + with open(args_output.last_tested, 'w', encoding='utf-8') as file_obj: + file_obj.write(str(svn_version)) + + +if __name__ == '__main__': + main() diff --git a/llvm_tools/update_packages_and_test_cq_unittest.py b/llvm_tools/update_packages_and_test_cq_unittest.py new file mode 100755 index 00000000..58f99e83 --- /dev/null +++ b/llvm_tools/update_packages_and_test_cq_unittest.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""Unittests for running tryjobs after updating packages.""" + +from __future__ import print_function + +import json +import unittest +import unittest.mock as mock + +from test_helpers import ArgsOutputTest +from test_helpers import CreateTemporaryFile +from update_chromeos_llvm_hash import CommitContents +import update_chromeos_llvm_hash +import update_packages_and_test_cq + + +class UpdatePackagesAndRunTestCQTest(unittest.TestCase): + """Unittests for CQ dry run after updating packages.""" + + def testGetCQDependString(self): + test_no_changelists = [] + test_single_changelist = [1234] + test_multiple_changelists = [1234, 5678] + + self.assertEqual( + update_packages_and_test_cq.GetCQDependString(test_no_changelists), + None) + + self.assertEqual( + update_packages_and_test_cq.GetCQDependString(test_single_changelist), + '\nCq-Depend: chromium:1234') + + self.assertEqual( + update_packages_and_test_cq.GetCQDependString( + test_multiple_changelists), + '\nCq-Depend: chromium:1234, chromium:5678') + + # Mock ExecCommandAndCaptureOutput for the gerrit command execution. + @mock.patch.object( + update_packages_and_test_cq, + 'ExecCommandAndCaptureOutput', + return_value=None) + def teststartCQDryRunNoDeps(self, mock_exec_cmd): + chroot_path = '/abs/path/to/chroot' + test_cl_number = 1000 + + # test with no deps cls. + extra_cls = [] + update_packages_and_test_cq.startCQDryRun(test_cl_number, extra_cls, + chroot_path) + + expected_gerrit_message = [ + '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', + str(test_cl_number), '1' + ] + + mock_exec_cmd.assert_called_once_with(expected_gerrit_message) + + # Mock ExecCommandAndCaptureOutput for the gerrit command execution. + @mock.patch.object( + update_packages_and_test_cq, + 'ExecCommandAndCaptureOutput', + return_value=None) + # test with a single deps cl. + def teststartCQDryRunSingleDep(self, mock_exec_cmd): + chroot_path = '/abs/path/to/chroot' + test_cl_number = 1000 + + extra_cls = [2000] + update_packages_and_test_cq.startCQDryRun(test_cl_number, extra_cls, + chroot_path) + + expected_gerrit_cmd_1 = [ + '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', + str(test_cl_number), '1' + ] + expected_gerrit_cmd_2 = [ + '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', + str(2000), '1' + ] + + self.assertEqual(mock_exec_cmd.call_count, 2) + self.assertEqual(mock_exec_cmd.call_args_list[0][0][0], + expected_gerrit_cmd_1) + self.assertEqual(mock_exec_cmd.call_args_list[1][0][0], + expected_gerrit_cmd_2) + + # Mock ExecCommandAndCaptureOutput for the gerrit command execution. + @mock.patch.object( + update_packages_and_test_cq, + 'ExecCommandAndCaptureOutput', + return_value=None) + def teststartCQDryRunMultipleDep(self, mock_exec_cmd): + chroot_path = '/abs/path/to/chroot' + test_cl_number = 1000 + + # test with multiple deps cls. + extra_cls = [3000, 4000] + update_packages_and_test_cq.startCQDryRun(test_cl_number, extra_cls, + chroot_path) + + expected_gerrit_cmd_1 = [ + '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', + str(test_cl_number), '1' + ] + expected_gerrit_cmd_2 = [ + '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', + str(3000), '1' + ] + expected_gerrit_cmd_3 = [ + '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', + str(4000), '1' + ] + + self.assertEqual(mock_exec_cmd.call_count, 3) + self.assertEqual(mock_exec_cmd.call_args_list[0][0][0], + expected_gerrit_cmd_1) + self.assertEqual(mock_exec_cmd.call_args_list[1][0][0], + expected_gerrit_cmd_2) + self.assertEqual(mock_exec_cmd.call_args_list[2][0][0], + expected_gerrit_cmd_3) + + +if __name__ == '__main__': + unittest.main() -- cgit v1.2.3