aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJian Cai <jiancai@google.com>2020-11-19 13:50:24 -0800
committerJian Cai <jiancai@google.com>2021-04-28 20:56:35 +0000
commit4a12a1285b9992b158afbbc79c91244da4b7cc4b (patch)
tree430d3827c9bed2a915aedbf80b455a6ae43014d5
parentb5bce2002ee5b944297c7d6c217e04a164f1aaae (diff)
downloadtoolchain-utils-4a12a1285b9992b158afbbc79c91244da4b7cc4b.tar.gz
llvm_tools: refactor automatic LLVM bisection
This removes update_all_tryjobs_with_auto.py and move a function to its only user module. Also stopped raising errors when a build is not ready for "cros buildresult" to avoid confusion. BUG=chromium:1151055 TEST=local and CQ tests. Change-Id: I1591d17f1fe76cf6fb223c18ab0c96349982f53c Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/toolchain-utils/+/2551918 Tested-by: Jian Cai <jiancai@google.com> Reviewed-by: Manoj Gupta <manojgupta@chromium.org>
-rw-r--r--llvm_tools/README.md24
-rwxr-xr-xllvm_tools/auto_llvm_bisection.py100
-rwxr-xr-xllvm_tools/auto_llvm_bisection_unittest.py304
-rwxr-xr-xllvm_tools/update_all_tryjobs_with_auto.py80
-rwxr-xr-xllvm_tools/update_tryjob_status.py79
-rwxr-xr-xllvm_tools/update_tryjob_status_unittest.py214
6 files changed, 284 insertions, 517 deletions
diff --git a/llvm_tools/README.md b/llvm_tools/README.md
index 8eff98ba..fabb37a9 100644
--- a/llvm_tools/README.md
+++ b/llvm_tools/README.md
@@ -360,30 +360,6 @@ $ ./update_tryjob_status.py \
--custom_script /abs/path/to/script.py
```
-### `update_all_tryjobs_with_auto.py`
-
-#### Usage
-
-This script updates all tryjobs that are 'pending' to the result provided by
-`cros buildresult`.
-
-For example:
-
-```
-$ ./update_all_tryjobs_with_auto.py \
- --last_tested /abs/path/to/last_tested_file.json \
- --chroot_path /abs/path/to/chroot
-```
-
-The above example will update all tryjobs whose 'status' is 'pending' in the
-file provided by `--last_tested`.
-
-For help with the command line arguments of the script, run:
-
-```
-$ ./update_all_tryjobs_with_auto.py --help
-```
-
### `modify_a_tryjob.py`
#### Usage
diff --git a/llvm_tools/auto_llvm_bisection.py b/llvm_tools/auto_llvm_bisection.py
index dd29cf4f..7e8fb1dd 100755
--- a/llvm_tools/auto_llvm_bisection.py
+++ b/llvm_tools/auto_llvm_bisection.py
@@ -8,6 +8,8 @@
from __future__ import print_function
+import enum
+import json
import os
import subprocess
import sys
@@ -17,7 +19,7 @@ import traceback
import chroot
from llvm_bisection import BisectionExitStatus
import llvm_bisection
-from update_all_tryjobs_with_auto import GetPathToUpdateAllTryjobsWithAutoScript
+import update_tryjob_status
# Used to re-try for 'llvm_bisection.py' to attempt to launch more tryjobs.
BISECTION_RETRY_TIME_SECS = 10 * 60
@@ -39,6 +41,52 @@ BISECTION_ATTEMPTS = 3
POLLING_LIMIT_SECS = 18 * 60 * 60
+class BuilderStatus(enum.Enum):
+ """Actual values given via 'cros buildresult'."""
+
+ PASS = 'pass'
+ FAIL = 'fail'
+ RUNNING = 'running'
+
+
+builder_status_mapping = {
+ BuilderStatus.PASS.value: update_tryjob_status.TryjobStatus.GOOD.value,
+ BuilderStatus.FAIL.value: update_tryjob_status.TryjobStatus.BAD.value,
+ BuilderStatus.RUNNING.value: update_tryjob_status.TryjobStatus.PENDING.value
+}
+
+
+def GetBuildResult(chroot_path, buildbucket_id):
+ """Returns the conversion of the result of 'cros buildresult'."""
+
+ # Calls 'cros buildresult' to get the status of the tryjob.
+ try:
+ tryjob_json = subprocess.check_output(
+ [
+ 'cros_sdk', '--', 'cros', 'buildresult', '--buildbucket-id',
+ str(buildbucket_id), '--report', 'json'
+ ],
+ cwd=chroot_path,
+ stderr=subprocess.STDOUT,
+ encoding='UTF-8',
+ )
+ except subprocess.CalledProcessError as err:
+ if 'No build found. Perhaps not started' not in err.output:
+ raise
+ return None
+
+ tryjob_content = json.loads(tryjob_json)
+
+ build_result = str(tryjob_content['%d' % buildbucket_id]['status'])
+
+ # The string returned by 'cros buildresult' might not be in the mapping.
+ if build_result not in builder_status_mapping:
+ raise ValueError('"cros buildresult" return value is invalid: %s' %
+ build_result)
+
+ return builder_status_mapping[build_result]
+
+
def main():
"""Bisects LLVM using the result of `cros buildresult` of each tryjob.
@@ -50,51 +98,58 @@ def main():
args_output = llvm_bisection.GetCommandLineArgs()
- exec_update_tryjobs = [
- GetPathToUpdateAllTryjobsWithAutoScript(), '--chroot_path',
- args_output.chroot_path, '--last_tested', args_output.last_tested
- ]
-
if os.path.isfile(args_output.last_tested):
print('Resuming bisection for %s' % args_output.last_tested)
else:
print('Starting a new bisection for %s' % args_output.last_tested)
while True:
+ # Update the status of existing tryjobs
if os.path.isfile(args_output.last_tested):
update_start_time = time.time()
-
- # Update all tryjobs whose status is 'pending' to the result of `cros
- # buildresult`.
+ with open(args_output.last_tested) as json_file:
+ json_dict = json.load(json_file)
while True:
print('\nAttempting to update all tryjobs whose "status" is '
'"pending":')
print('-' * 40)
- update_ret = subprocess.call(exec_update_tryjobs)
+ completed = True
+ for tryjob in json_dict['jobs']:
+ if tryjob[
+ 'status'] == update_tryjob_status.TryjobStatus.PENDING.value:
+ status = GetBuildResult(args_output.chroot_path,
+ tryjob['buildbucket_id'])
+ if status:
+ tryjob['status'] = status
+ else:
+ completed = False
print('-' * 40)
- # Successfully updated all tryjobs whose 'status' was 'pending'/ no
- # updates were needed (all tryjobs already have been updated).
- if update_ret == 0:
+ # Proceed to the next step if all the existing tryjobs have completed.
+ if completed:
break
delta_time = time.time() - update_start_time
if delta_time > POLLING_LIMIT_SECS:
- print('Unable to update tryjobs whose status is "pending" to '
- 'the result of `cros buildresult`.')
-
# Something is wrong with updating the tryjobs's 'status' via
# `cros buildresult` (e.g. network issue, etc.).
- sys.exit(1)
+ sys.exit('Failed to update pending tryjobs.')
+ print('-' * 40)
print('Sleeping for %d minutes.' % (POLL_RETRY_TIME_SECS // 60))
time.sleep(POLL_RETRY_TIME_SECS)
- # Launch more tryjobs if possible to narrow down the bad commit/revision or
- # terminate the bisection because the bad commit/revision was found.
+ # There should always be update from the tryjobs launched in the
+ # last iteration.
+ temp_filename = '%s.new' % args_output.last_tested
+ with open(temp_filename, 'w') as temp_file:
+ json.dump(json_dict, temp_file, indent=4, separators=(',', ': '))
+ os.rename(temp_filename, args_output.last_tested)
+
+ # Launch more tryjobs.
for cur_try in range(1, BISECTION_ATTEMPTS + 1):
try:
print('\nAttempting to launch more tryjobs if possible:')
@@ -104,8 +159,7 @@ def main():
print('-' * 40)
- # Exit code 126 means that there are no more revisions to test between
- # 'start' and 'end', so bisection is complete.
+ # Stop if the bisection has completed.
if bisection_ret == BisectionExitStatus.BISECTION_COMPLETE.value:
sys.exit(0)
@@ -118,9 +172,7 @@ def main():
# Exceeded the number of times to launch more tryjobs.
if cur_try == BISECTION_ATTEMPTS:
- print('Unable to continue bisection.')
-
- sys.exit(1)
+ sys.exit('Unable to continue bisection.')
num_retries_left = BISECTION_ATTEMPTS - cur_try
diff --git a/llvm_tools/auto_llvm_bisection_unittest.py b/llvm_tools/auto_llvm_bisection_unittest.py
index 56b556e6..07c0e715 100755
--- a/llvm_tools/auto_llvm_bisection_unittest.py
+++ b/llvm_tools/auto_llvm_bisection_unittest.py
@@ -8,6 +8,7 @@
from __future__ import print_function
+import json
import os
import subprocess
import time
@@ -19,187 +20,128 @@ import auto_llvm_bisection
import chroot
import llvm_bisection
import test_helpers
+import update_tryjob_status
class AutoLLVMBisectionTest(unittest.TestCase):
"""Unittests for auto bisection of LLVM."""
- # Simulate the behavior of `VerifyOutsideChroot()` when successfully invoking
- # the script outside of the chroot.
@mock.patch.object(chroot, 'VerifyOutsideChroot', return_value=True)
- # Simulate behavior of `time.sleep()` when waiting for errors to settle caused
- # by `llvm_bisection.main()` (e.g. network issue, etc.).
- @mock.patch.object(time, 'sleep')
- # Simulate behavior of `traceback.print_exc()` when an exception happened in
- # `llvm_bisection.main()`.
- @mock.patch.object(traceback, 'print_exc')
- # Simulate behavior of `llvm_bisection.main()` when failed to launch tryjobs
- # (exception happened along the way, etc.).
- @mock.patch.object(llvm_bisection, 'main')
- # Simulate behavior of `os.path.isfile()` when starting a new bisection.
- @mock.patch.object(os.path, 'isfile', return_value=False)
- # Simulate behavior of `GetPathToUpdateAllTryjobsWithAutoScript()` when
- # returning the absolute path to that script that updates all 'pending'
- # tryjobs to the result of `cros buildresult`.
- @mock.patch.object(
- auto_llvm_bisection,
- 'GetPathToUpdateAllTryjobsWithAutoScript',
- return_value='/abs/path/to/update_tryjob.py')
- # Simulate `llvm_bisection.GetCommandLineArgs()` when parsing the command line
- # arguments required by the bisection script.
@mock.patch.object(
llvm_bisection,
'GetCommandLineArgs',
return_value=test_helpers.ArgsOutputTest())
- def testFailedToStartBisection(
- self, mock_get_args, mock_get_auto_script, mock_is_file,
- mock_llvm_bisection, mock_traceback, mock_sleep, mock_outside_chroot):
-
- def MockLLVMBisectionRaisesException(_args_output):
- raise ValueError('Failed to launch more tryjobs.')
-
- # Use the test function to simulate the behavior of an exception happening
- # when launching more tryjobs.
- mock_llvm_bisection.side_effect = MockLLVMBisectionRaisesException
+ @mock.patch.object(time, 'sleep')
+ @mock.patch.object(traceback, 'print_exc')
+ @mock.patch.object(llvm_bisection, 'main')
+ @mock.patch.object(os.path, 'isfile')
+ @mock.patch.object(auto_llvm_bisection, 'open')
+ @mock.patch.object(json, 'load')
+ @mock.patch.object(auto_llvm_bisection, 'GetBuildResult')
+ @mock.patch.object(os, 'rename')
+ def testAutoLLVMBisectionPassed(
+ self,
+ # pylint: disable=unused-argument
+ mock_rename,
+ mock_get_build_result,
+ mock_json_load,
+ # pylint: disable=unused-argument
+ mock_open,
+ mock_isfile,
+ mock_llvm_bisection,
+ mock_traceback,
+ mock_sleep,
+ mock_get_args,
+ mock_outside_chroot):
+
+ mock_isfile.side_effect = [False, False, True, True]
+ mock_llvm_bisection.side_effect = [
+ 0,
+ ValueError('Failed to launch more tryjobs.'),
+ llvm_bisection.BisectionExitStatus.BISECTION_COMPLETE.value
+ ]
+ mock_json_load.return_value = {
+ 'start':
+ 369410,
+ 'end':
+ 369420,
+ 'jobs': [{
+ 'buildbucket_id': 12345,
+ 'rev': 369411,
+ 'status': update_tryjob_status.TryjobStatus.PENDING.value,
+ }]
+ }
+ mock_get_build_result.return_value = (
+ update_tryjob_status.TryjobStatus.GOOD.value)
- # Verify the exception is raised when the number of attempts to launched
- # more tryjobs is exceeded, so unable to continue
- # bisection.
+ # Verify the excpetion is raised when successfully found the bad revision.
+ # Uses `sys.exit(0)` to indicate success.
with self.assertRaises(SystemExit) as err:
auto_llvm_bisection.main()
- self.assertEqual(err.exception.code, 1)
+ self.assertEqual(err.exception.code, 0)
mock_outside_chroot.assert_called_once()
mock_get_args.assert_called_once()
- mock_get_auto_script.assert_called_once()
- self.assertEqual(mock_is_file.call_count, 2)
+ self.assertEqual(mock_isfile.call_count, 3)
self.assertEqual(mock_llvm_bisection.call_count, 3)
- self.assertEqual(mock_traceback.call_count, 3)
- self.assertEqual(mock_sleep.call_count, 2)
+ mock_traceback.assert_called_once()
+ mock_sleep.assert_called_once()
- # Simulate the behavior of `subprocess.call()` when successfully updated all
- # tryjobs whose 'status' value is 'pending'.
- @mock.patch.object(subprocess, 'call', return_value=0)
- # Simulate the behavior of `VerifyOutsideChroot()` when successfully invoking
- # the script outside of the chroot.
@mock.patch.object(chroot, 'VerifyOutsideChroot', return_value=True)
- # Simulate behavior of `time.sleep()` when waiting for errors to settle caused
- # by `llvm_bisection.main()` (e.g. network issue, etc.).
@mock.patch.object(time, 'sleep')
- # Simulate behavior of `traceback.print_exc()` when an exception happened in
- # `llvm_bisection.main()`.
@mock.patch.object(traceback, 'print_exc')
- # Simulate behavior of `llvm_bisection.main()` when failed to launch tryjobs
- # (exception happened along the way, etc.).
@mock.patch.object(llvm_bisection, 'main')
- # Simulate behavior of `os.path.isfile()` when starting a new bisection.
@mock.patch.object(os.path, 'isfile')
- # Simulate behavior of `GetPathToUpdateAllTryjobsWithAutoScript()` when
- # returning the absolute path to that script that updates all 'pending'
- # tryjobs to the result of `cros buildresult`.
- @mock.patch.object(
- auto_llvm_bisection,
- 'GetPathToUpdateAllTryjobsWithAutoScript',
- return_value='/abs/path/to/update_tryjob.py')
- # Simulate `llvm_bisection.GetCommandLineArgs()` when parsing the command line
- # arguments required by the bisection script.
@mock.patch.object(
llvm_bisection,
'GetCommandLineArgs',
return_value=test_helpers.ArgsOutputTest())
- def testSuccessfullyBisectedLLVMRevision(
- self, mock_get_args, mock_get_auto_script, mock_is_file,
- mock_llvm_bisection, mock_traceback, mock_sleep, mock_outside_chroot,
- mock_update_tryjobs):
-
- # Simulate the behavior of `os.path.isfile()` when checking whether the
- # status file provided exists.
- @test_helpers.CallCountsToMockFunctions
- def MockStatusFileCheck(call_count, _last_tested):
- # Simulate that the status file does not exist, so the LLVM bisection
- # script would create the status file and launch tryjobs.
- if call_count < 2:
- return False
-
- # Simulate when the status file exists and `subprocess.call()` executes
- # the script that updates all the 'pending' tryjobs to the result of `cros
- # buildresult`.
- if call_count == 2:
- return True
-
- assert False, 'os.path.isfile() called more times than expected.'
-
- # Simulate behavior of `llvm_bisection.main()` when successfully bisected
- # between the good and bad LLVM revision.
- @test_helpers.CallCountsToMockFunctions
- def MockLLVMBisectionReturnValue(call_count, _args_output):
- # Simulate that successfully launched more tryjobs.
- if call_count == 0:
- return 0
-
- # Simulate that failed to launch more tryjobs.
- if call_count == 1:
- raise ValueError('Failed to launch more tryjobs.')
-
- # Simulate that the bad revision has been found.
- if call_count == 2:
- return llvm_bisection.BisectionExitStatus.BISECTION_COMPLETE.value
+ def testFailedToStartBisection(self, mock_get_args, mock_isfile,
+ mock_llvm_bisection, mock_traceback,
+ mock_sleep, mock_outside_chroot):
- assert False, 'Called `llvm_bisection.main()` more than expected.'
+ mock_isfile.return_value = False
+ mock_llvm_bisection.side_effect = ValueError(
+ 'Failed to launch more tryjobs.')
- # Use the test function to simulate the behavior of `llvm_bisection.main()`.
- mock_llvm_bisection.side_effect = MockLLVMBisectionReturnValue
-
- # Use the test function to simulate the behavior of `os.path.isfile()`.
- mock_is_file.side_effect = MockStatusFileCheck
-
- # Verify the excpetion is raised when successfully found the bad revision.
- # Uses `sys.exit(0)` to indicate success.
+ # Verify the exception is raised when the number of attempts to launched
+ # more tryjobs is exceeded, so unable to continue
+ # bisection.
with self.assertRaises(SystemExit) as err:
auto_llvm_bisection.main()
- self.assertEqual(err.exception.code, 0)
+ self.assertEqual(err.exception.code, 'Unable to continue bisection.')
mock_outside_chroot.assert_called_once()
mock_get_args.assert_called_once()
- mock_get_auto_script.assert_called_once()
- self.assertEqual(mock_is_file.call_count, 3)
+ self.assertEqual(mock_isfile.call_count, 2)
self.assertEqual(mock_llvm_bisection.call_count, 3)
- mock_traceback.assert_called_once()
- mock_sleep.assert_called_once()
- mock_update_tryjobs.assert_called_once()
+ self.assertEqual(mock_traceback.call_count, 3)
+ self.assertEqual(mock_sleep.call_count, 2)
- # Simulate behavior of `subprocess.call()` when failed to update tryjobs to
- # `cros buildresult` (script failed).
- @mock.patch.object(subprocess, 'call', return_value=1)
- # Simulate behavior of `time.time()` when determining the time passed when
- # updating tryjobs whose 'status' is 'pending'.
- @mock.patch.object(time, 'time')
- # Simulate the behavior of `VerifyOutsideChroot()` when successfully invoking
- # the script outside of the chroot.
@mock.patch.object(chroot, 'VerifyOutsideChroot', return_value=True)
- # Simulate behavior of `time.sleep()` when waiting for errors to settle caused
- # by `llvm_bisection.main()` (e.g. network issue, etc.).
- @mock.patch.object(time, 'sleep')
- # Simulate behavior of `traceback.print_exc()` when resuming bisection.
- @mock.patch.object(os.path, 'isfile', return_value=True)
- # Simulate behavior of `GetPathToUpdateAllTryjobsWithAutoScript()` when
- # returning the absolute path to that script that updates all 'pending'
- # tryjobs to the result of `cros buildresult`.
- @mock.patch.object(
- auto_llvm_bisection,
- 'GetPathToUpdateAllTryjobsWithAutoScript',
- return_value='/abs/path/to/update_tryjob.py')
- # Simulate `llvm_bisection.GetCommandLineArgs()` when parsing the command line
- # arguments required by the bisection script.
@mock.patch.object(
llvm_bisection,
'GetCommandLineArgs',
return_value=test_helpers.ArgsOutputTest())
+ @mock.patch.object(time, 'time')
+ @mock.patch.object(time, 'sleep')
+ @mock.patch.object(os.path, 'isfile')
+ @mock.patch.object(auto_llvm_bisection, 'open')
+ @mock.patch.object(json, 'load')
+ @mock.patch.object(auto_llvm_bisection, 'GetBuildResult')
def testFailedToUpdatePendingTryJobs(
- self, mock_get_args, mock_get_auto_script, mock_is_file, mock_sleep,
- mock_outside_chroot, mock_time, mock_update_tryjobs):
+ self,
+ mock_get_build_result,
+ mock_json_load,
+ # pylint: disable=unused-argument
+ mock_open,
+ mock_isfile,
+ mock_sleep,
+ mock_time,
+ mock_get_args,
+ mock_outside_chroot):
# Simulate behavior of `time.time()` for time passed.
@test_helpers.CallCountsToMockFunctions
@@ -209,9 +151,20 @@ class AutoLLVMBisectionTest(unittest.TestCase):
assert False, 'Called `time.time()` more than expected.'
- # Use the test function to simulate the behavior of `time.time()`.
+ mock_isfile.return_value = True
+ mock_json_load.return_value = {
+ 'start':
+ 369410,
+ 'end':
+ 369420,
+ 'jobs': [{
+ 'buildbucket_id': 12345,
+ 'rev': 369411,
+ 'status': update_tryjob_status.TryjobStatus.PENDING.value,
+ }]
+ }
+ mock_get_build_result.return_value = None
mock_time.side_effect = MockTimePassed
-
# Reduce the polling limit for the test case to terminate faster.
auto_llvm_bisection.POLLING_LIMIT_SECS = 1
@@ -220,15 +173,80 @@ class AutoLLVMBisectionTest(unittest.TestCase):
with self.assertRaises(SystemExit) as err:
auto_llvm_bisection.main()
- self.assertEqual(err.exception.code, 1)
+ self.assertEqual(err.exception.code, 'Failed to update pending tryjobs.')
mock_outside_chroot.assert_called_once()
mock_get_args.assert_called_once()
- mock_get_auto_script.assert_called_once()
- self.assertEqual(mock_is_file.call_count, 2)
+ self.assertEqual(mock_isfile.call_count, 2)
mock_sleep.assert_called_once()
self.assertEqual(mock_time.call_count, 3)
- self.assertEqual(mock_update_tryjobs.call_count, 2)
+
+ @mock.patch.object(subprocess, 'check_output')
+ def testGetBuildResult(self, mock_chroot_command):
+ buildbucket_id = 192
+ status = auto_llvm_bisection.BuilderStatus.PASS.value
+ tryjob_contents = {buildbucket_id: {'status': status}}
+ mock_chroot_command.return_value = json.dumps(tryjob_contents)
+ chroot_path = '/some/path/to/chroot'
+
+ self.assertEqual(
+ auto_llvm_bisection.GetBuildResult(chroot_path, buildbucket_id),
+ update_tryjob_status.TryjobStatus.GOOD.value)
+
+ mock_chroot_command.assert_called_once_with(
+ [
+ 'cros_sdk', '--', 'cros', 'buildresult', '--buildbucket-id',
+ str(buildbucket_id), '--report', 'json'
+ ],
+ cwd='/some/path/to/chroot',
+ stderr=subprocess.STDOUT,
+ encoding='UTF-8',
+ )
+
+ @mock.patch.object(subprocess, 'check_output')
+ def testGetBuildResultPassedWithUnstartedTryjob(self, mock_chroot_command):
+ buildbucket_id = 192
+ chroot_path = '/some/path/to/chroot'
+ mock_chroot_command.side_effect = subprocess.CalledProcessError(
+ returncode=1, cmd=[], output='No build found. Perhaps not started')
+ auto_llvm_bisection.GetBuildResult(chroot_path, buildbucket_id)
+ mock_chroot_command.assert_called_once_with(
+ [
+ 'cros_sdk', '--', 'cros', 'buildresult', '--buildbucket-id', '192',
+ '--report', 'json'
+ ],
+ cwd=chroot_path,
+ stderr=subprocess.STDOUT,
+ encoding='UTF-8',
+ )
+
+ @mock.patch.object(subprocess, 'check_output')
+ def testGetBuildReusultFailedWithInvalidBuildStatus(self,
+ mock_chroot_command):
+ chroot_path = '/some/path/to/chroot'
+ buildbucket_id = 50
+ invalid_build_status = 'querying'
+ tryjob_contents = {buildbucket_id: {'status': invalid_build_status}}
+ mock_chroot_command.return_value = json.dumps(tryjob_contents)
+
+ # Verify the exception is raised when the return value of `cros buildresult`
+ # is not in the `builder_status_mapping`.
+ with self.assertRaises(ValueError) as err:
+ auto_llvm_bisection.GetBuildResult(chroot_path, buildbucket_id)
+
+ self.assertEqual(
+ str(err.exception),
+ '"cros buildresult" return value is invalid: %s' % invalid_build_status)
+
+ mock_chroot_command.assert_called_once_with(
+ [
+ 'cros_sdk', '--', 'cros', 'buildresult', '--buildbucket-id',
+ str(buildbucket_id), '--report', 'json'
+ ],
+ cwd=chroot_path,
+ stderr=subprocess.STDOUT,
+ encoding='UTF-8',
+ )
if __name__ == '__main__':
diff --git a/llvm_tools/update_all_tryjobs_with_auto.py b/llvm_tools/update_all_tryjobs_with_auto.py
deleted file mode 100755
index 11e67ed4..00000000
--- a/llvm_tools/update_all_tryjobs_with_auto.py
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-# Copyright 2019 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.
-
-"""Updates the status of all tryjobs to the result of `cros buildresult`."""
-
-from __future__ import print_function
-
-import argparse
-import json
-import os
-
-import chroot
-import update_tryjob_status
-
-
-def GetPathToUpdateAllTryjobsWithAutoScript():
- """Returns the absolute path to this script."""
-
- return os.path.abspath(__file__)
-
-
-def GetCommandLineArgs():
- """Parses the command line for the command line arguments."""
-
- # Default absoute path to the chroot if 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=__doc__)
-
- # Add argument for the JSON file to use for the update of a tryjob.
- parser.add_argument(
- '--last_tested',
- required=True,
- help='The absolute path to the JSON file that contains the tryjobs used '
- 'for bisecting LLVM.')
-
- # Add argument for a specific chroot path.
- parser.add_argument(
- '--chroot_path',
- default=cros_root,
- help='the path to the chroot (default: %(default)s)')
-
- args_output = parser.parse_args()
-
- if not os.path.isfile(args_output.last_tested) or \
- not args_output.last_tested.endswith('.json'):
- raise ValueError('File does not exist or does not ending in ".json" '
- ': %s' % args_output.last_tested)
-
- return args_output
-
-
-def main():
- """Updates the status of a tryjob."""
-
- chroot.VerifyOutsideChroot()
-
- args_output = GetCommandLineArgs()
-
- with open(args_output.last_tested) as tryjobs:
- bisect_contents = json.load(tryjobs)
-
- for tryjob in bisect_contents['jobs']:
- if tryjob['status'] == update_tryjob_status.TryjobStatus.PENDING.value:
- tryjob['status'] = update_tryjob_status.GetAutoResult(
- args_output.chroot_path, tryjob['buildbucket_id'])
-
- new_file = '%s.new' % args_output.last_tested
- with open(new_file, 'w') as update_tryjobs:
- json.dump(bisect_contents, update_tryjobs, indent=4, separators=(',', ': '))
- os.rename(new_file, args_output.last_tested)
-
-
-if __name__ == '__main__':
- main()
diff --git a/llvm_tools/update_tryjob_status.py b/llvm_tools/update_tryjob_status.py
index f1500364..f25fadca 100755
--- a/llvm_tools/update_tryjob_status.py
+++ b/llvm_tools/update_tryjob_status.py
@@ -16,7 +16,6 @@ import subprocess
import sys
import chroot
-from subprocess_helpers import ChrootRunCommand
from test_helpers import CreateTemporaryJsonFile
@@ -32,17 +31,6 @@ class TryjobStatus(enum.Enum):
# determines the 'status' value of the tryjob).
CUSTOM_SCRIPT = 'custom_script'
- # Uses the result returned by 'cros buildresult'.
- AUTO = 'auto'
-
-
-class BuilderStatus(enum.Enum):
- """Actual values given via 'cros buildresult'."""
-
- PASS = 'pass'
- FAIL = 'fail'
- RUNNING = 'running'
-
class CustomScriptStatus(enum.Enum):
"""Exit code values of a custom script."""
@@ -66,12 +54,6 @@ custom_script_exit_value_mapping = {
CustomScriptStatus.SKIP.value: TryjobStatus.SKIP.value
}
-builder_status_mapping = {
- BuilderStatus.PASS.value: TryjobStatus.GOOD.value,
- BuilderStatus.FAIL.value: TryjobStatus.BAD.value,
- BuilderStatus.RUNNING.value: TryjobStatus.PENDING.value
-}
-
def GetCommandLineArgs():
"""Parses the command line for the command line arguments."""
@@ -106,12 +88,6 @@ def GetCommandLineArgs():
type=int,
help='The revision to set its status.')
- # 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 the custom script to execute for the 'custom_script'
# option in '--set_status'.
parser.add_argument(
@@ -123,13 +99,14 @@ def GetCommandLineArgs():
args_output = parser.parse_args()
- if not os.path.isfile(args_output.status_file) or \
- not args_output.status_file.endswith('.json'):
+ if not (os.path.isfile(
+ args_output.status_file and
+ not args_output.status_file.endswith('.json'))):
raise ValueError('File does not exist or does not ending in ".json" '
': %s' % args_output.status_file)
- if args_output.set_status == TryjobStatus.CUSTOM_SCRIPT.value and \
- not args_output.custom_script:
+ if (args_output.set_status == TryjobStatus.CUSTOM_SCRIPT.value and
+ not args_output.custom_script):
raise ValueError('Please provide the absolute path to the script to '
'execute.')
@@ -165,35 +142,6 @@ def FindTryjobIndex(revision, tryjobs_list):
return None
-def GetStatusFromCrosBuildResult(chroot_path, buildbucket_id):
- """Retrieves the 'status' using 'cros buildresult'."""
-
- get_buildbucket_id_cmd = [
- 'cros', 'buildresult', '--buildbucket-id',
- str(buildbucket_id), '--report', 'json'
- ]
-
- tryjob_json = ChrootRunCommand(chroot_path, get_buildbucket_id_cmd)
-
- tryjob_contents = json.loads(tryjob_json)
-
- return str(tryjob_contents['%d' % buildbucket_id]['status'])
-
-
-def GetAutoResult(chroot_path, buildbucket_id):
- """Returns the conversion of the result of 'cros buildresult'."""
-
- # Calls 'cros buildresult' to get the status of the tryjob.
- build_result = GetStatusFromCrosBuildResult(chroot_path, buildbucket_id)
-
- # The string returned by 'cros buildresult' might not be in the mapping.
- if build_result not in builder_status_mapping:
- raise ValueError(
- '"cros buildresult" return value is invalid: %s' % build_result)
-
- return builder_status_mapping[build_result]
-
-
def GetCustomScriptResult(custom_script, status_file, tryjob_contents):
"""Returns the conversion of the exit code of the custom script.
@@ -245,18 +193,15 @@ def GetCustomScriptResult(custom_script, status_file, tryjob_contents):
return custom_script_exit_value_mapping[exec_script_cmd_obj.returncode]
-def UpdateTryjobStatus(revision, set_status, status_file, chroot_path,
- custom_script):
+def UpdateTryjobStatus(revision, set_status, status_file, custom_script):
"""Updates a tryjob's 'status' field based off of 'set_status'.
Args:
revision: The revision associated with the tryjob.
set_status: What to update the 'status' field to.
Ex: TryjobStatus.Good, TryjobStatus.BAD, TryjobStatus.PENDING, or
- TryjobStatus.AUTO where TryjobStatus.AUTO uses the result of
- 'cros buildresult'.
+ TryjobStatus.
status_file: The .JSON file that contains the tryjobs.
- chroot_path: The absolute path to the chroot (used by 'cros buildresult').
custom_script: The absolute path to a script that will be executed which
will determine the 'status' value of the tryjob.
"""
@@ -282,8 +227,8 @@ def UpdateTryjobStatus(revision, set_status, status_file, chroot_path,
# 'FindTryjobIndex()' returns None if the revision was not found.
if tryjob_index is None:
- raise ValueError(
- 'Unable to find tryjob for %d in %s' % (revision, status_file))
+ raise ValueError('Unable to find tryjob for %d in %s' %
+ (revision, status_file))
# Set 'status' depending on 'set_status' for the tryjob.
if set_status == TryjobStatus.GOOD:
@@ -292,9 +237,6 @@ def UpdateTryjobStatus(revision, set_status, status_file, chroot_path,
bisect_contents['jobs'][tryjob_index]['status'] = TryjobStatus.BAD.value
elif set_status == TryjobStatus.PENDING:
bisect_contents['jobs'][tryjob_index]['status'] = TryjobStatus.PENDING.value
- elif set_status == TryjobStatus.AUTO:
- bisect_contents['jobs'][tryjob_index]['status'] = GetAutoResult(
- chroot_path, bisect_contents['jobs'][tryjob_index]['buildbucket_id'])
elif set_status == TryjobStatus.SKIP:
bisect_contents['jobs'][tryjob_index]['status'] = TryjobStatus.SKIP.value
elif set_status == TryjobStatus.CUSTOM_SCRIPT:
@@ -315,8 +257,7 @@ def main():
args_output = GetCommandLineArgs()
UpdateTryjobStatus(args_output.revision, TryjobStatus(args_output.set_status),
- args_output.status_file, args_output.chroot_path,
- args_output.custom_script)
+ args_output.status_file, args_output.custom_script)
if __name__ == '__main__':
diff --git a/llvm_tools/update_tryjob_status_unittest.py b/llvm_tools/update_tryjob_status_unittest.py
index b5e6556c..c42c6718 100755
--- a/llvm_tools/update_tryjob_status_unittest.py
+++ b/llvm_tools/update_tryjob_status_unittest.py
@@ -31,14 +31,13 @@ class UpdateTryjobStatusTest(unittest.TestCase):
'cl': 'https://some_link_to_tryjob.com',
'status': 'good',
'buildbucket_id': 91835
- },
- {
- 'rev': 1000,
- 'url': 'https://some_url_to_CL.com',
- 'cl': 'https://some_link_to_tryjob.com',
- 'status': 'pending',
- 'buildbucket_id': 10931
- }]
+ }, {
+ 'rev': 1000,
+ 'url': 'https://some_url_to_CL.com',
+ 'cl': 'https://some_link_to_tryjob.com',
+ 'status': 'pending',
+ 'buildbucket_id': 10931
+ }]
expected_index = 0
@@ -55,87 +54,19 @@ class UpdateTryjobStatusTest(unittest.TestCase):
'cl': 'https://some_link_to_tryjob.com',
'status': 'bad',
'buildbucket_id': 390
- },
- {
- 'rev': 10,
- 'url': 'https://some_url_to_CL.com',
- 'cl': 'https://some_link_to_tryjob.com',
- 'status': 'skip',
- 'buildbucket_id': 10
- }]
+ }, {
+ 'rev': 10,
+ 'url': 'https://some_url_to_CL.com',
+ 'cl': 'https://some_link_to_tryjob.com',
+ 'status': 'skip',
+ 'buildbucket_id': 10
+ }]
revision_to_find = 250
self.assertIsNone(
update_tryjob_status.FindTryjobIndex(revision_to_find, test_tryjobs))
- # Simulate the behavior of `ChrootRunCommand()` when executing a command
- # inside the chroot.
- @mock.patch.object(update_tryjob_status, 'ChrootRunCommand')
- def testGetStatusFromCrosBuildResult(self, mock_chroot_command):
- tryjob_contents = {
- '192': {
- 'status': 'good',
- 'CleanUpChroot': 'pass',
- 'artifacts_url': None
- }
- }
-
- # Use the test function to simulate 'ChrootRunCommand()' behavior.
- mock_chroot_command.return_value = json.dumps(tryjob_contents)
-
- buildbucket_id = 192
-
- chroot_path = '/some/path/to/chroot'
-
- self.assertEqual(
- update_tryjob_status.GetStatusFromCrosBuildResult(
- chroot_path, buildbucket_id), 'good')
-
- expected_cmd = [
- 'cros', 'buildresult', '--buildbucket-id',
- str(buildbucket_id), '--report', 'json'
- ]
-
- mock_chroot_command.assert_called_once_with(chroot_path, expected_cmd)
-
- # Simulate the behavior of `GetStatusFromCrosBuildResult()` when `cros
- # buildresult` returned a string that is not in the mapping.
- @mock.patch.object(
- update_tryjob_status,
- 'GetStatusFromCrosBuildResult',
- return_value='querying')
- def testInvalidCrosBuildResultValue(self, mock_cros_buildresult):
- chroot_path = '/some/path/to/chroot'
- buildbucket_id = 50
-
- # Verify the exception is raised when the return value of `cros buildresult`
- # is not in the `builder_status_mapping`.
- with self.assertRaises(ValueError) as err:
- update_tryjob_status.GetAutoResult(chroot_path, buildbucket_id)
-
- self.assertEqual(
- str(err.exception),
- '"cros buildresult" return value is invalid: querying')
-
- mock_cros_buildresult.assert_called_once_with(chroot_path, buildbucket_id)
-
- # Simulate the behavior of `GetStatusFromCrosBuildResult()` when `cros
- # buildresult` returned a string that is in the mapping.
- @mock.patch.object(
- update_tryjob_status,
- 'GetStatusFromCrosBuildResult',
- return_value=update_tryjob_status.BuilderStatus.PASS.value)
- def testValidCrosBuildResultValue(self, mock_cros_buildresult):
- chroot_path = '/some/path/to/chroot'
- buildbucket_id = 100
-
- self.assertEqual(
- update_tryjob_status.GetAutoResult(chroot_path, buildbucket_id),
- TryjobStatus.GOOD.value)
-
- mock_cros_buildresult.assert_called_once_with(chroot_path, buildbucket_id)
-
@mock.patch.object(subprocess, 'Popen')
# Simulate the behavior of `os.rename()` when successfully renamed a file.
@mock.patch.object(os, 'rename', return_value=None)
@@ -186,8 +117,9 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# does not match any of the exit codes in the mapping of
# `custom_script_exit_value_mapping`.
with self.assertRaises(ValueError) as err:
- update_tryjob_status.GetCustomScriptResult(
- custom_script_path, status_file_path, tryjob_contents)
+ update_tryjob_status.GetCustomScriptResult(custom_script_path,
+ status_file_path,
+ tryjob_contents)
self.assertEqual(str(err.exception), expected_error_message)
@@ -212,8 +144,8 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# `Popen.communicate()` returns a tuple of `stdout` and `stderr`.
mock_exec_custom_script.return_value.communicate.return_value = (None, None)
- mock_exec_custom_script.return_value.returncode = \
- CustomScriptStatus.GOOD.value
+ mock_exec_custom_script.return_value.returncode = (
+ CustomScriptStatus.GOOD.value)
tryjob_contents = {
'status': 'good',
@@ -226,8 +158,9 @@ class UpdateTryjobStatusTest(unittest.TestCase):
status_file_path = '/abs/path/to/status_file.json'
self.assertEqual(
- update_tryjob_status.GetCustomScriptResult(
- custom_script_path, status_file_path, tryjob_contents),
+ update_tryjob_status.GetCustomScriptResult(custom_script_path,
+ status_file_path,
+ tryjob_contents),
TryjobStatus.GOOD.value)
mock_exec_custom_script.assert_called_once()
@@ -247,16 +180,14 @@ class UpdateTryjobStatusTest(unittest.TestCase):
revision_to_update = 369412
- chroot_path = '/abs/path/to/chroot'
-
custom_script = None
# Verify the exception is raised when the `status_file` does not have any
# `jobs` (empty).
with self.assertRaises(SystemExit) as err:
- update_tryjob_status.UpdateTryjobStatus(
- revision_to_update, TryjobStatus.GOOD, temp_json_file, chroot_path,
- custom_script)
+ update_tryjob_status.UpdateTryjobStatus(revision_to_update,
+ TryjobStatus.GOOD,
+ temp_json_file, custom_script)
self.assertEqual(str(err.exception), 'No tryjobs in %s' % temp_json_file)
@@ -283,16 +214,14 @@ class UpdateTryjobStatusTest(unittest.TestCase):
revision_to_update = 369416
- chroot_path = '/abs/path/to/chroot'
-
custom_script = None
# Verify the exception is raised when the `status_file` does not have any
# `jobs` (empty).
with self.assertRaises(ValueError) as err:
- update_tryjob_status.UpdateTryjobStatus(
- revision_to_update, TryjobStatus.SKIP, temp_json_file, chroot_path,
- custom_script)
+ update_tryjob_status.UpdateTryjobStatus(revision_to_update,
+ TryjobStatus.SKIP,
+ temp_json_file, custom_script)
self.assertEqual(
str(err.exception), 'Unable to find tryjob for %d in %s' %
@@ -324,13 +253,11 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
- chroot_path = '/abs/path/to/chroot'
-
custom_script = None
update_tryjob_status.UpdateTryjobStatus(revision_to_update,
TryjobStatus.GOOD, temp_json_file,
- chroot_path, custom_script)
+ custom_script)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
@@ -365,13 +292,11 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
- chroot_path = '/abs/path/to/chroot'
-
custom_script = None
update_tryjob_status.UpdateTryjobStatus(revision_to_update,
TryjobStatus.BAD, temp_json_file,
- chroot_path, custom_script)
+ custom_script)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
@@ -407,13 +332,11 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
- chroot_path = '/abs/path/to/chroot'
-
custom_script = None
update_tryjob_status.UpdateTryjobStatus(
revision_to_update, update_tryjob_status.TryjobStatus.SKIP,
- temp_json_file, chroot_path, custom_script)
+ temp_json_file, custom_script)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
@@ -448,13 +371,11 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
- chroot_path = '/abs/path/to/chroot'
-
custom_script = None
update_tryjob_status.UpdateTryjobStatus(
revision_to_update, update_tryjob_status.TryjobStatus.PENDING,
- temp_json_file, chroot_path, custom_script)
+ temp_json_file, custom_script)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
@@ -465,69 +386,12 @@ class UpdateTryjobStatusTest(unittest.TestCase):
mock_find_tryjob_index.assert_called_once()
- # Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
- # status file.
- @mock.patch.object(update_tryjob_status, 'FindTryjobIndex', return_value=0)
- # Simulate the behavior of `GetAutoResult()` when `cros buildresult` returns
- # a value that is in the mapping.
- @mock.patch.object(
- update_tryjob_status,
- 'GetAutoResult',
- return_value=TryjobStatus.GOOD.value)
- def testSuccessfullyUpdatedTryjobStatusToAuto(self, mock_get_auto_result,
- mock_find_tryjob_index):
- bisect_test_contents = {
- 'start': 369410,
- 'end': 369420,
- 'jobs': [{
- 'rev': 369411,
- 'status': 'pending',
- 'buildbucket_id': 1200
- }]
- }
-
- # Create a temporary .JSON file to simulate a .JSON file that has bisection
- # contents.
- with CreateTemporaryJsonFile() as temp_json_file:
- with open(temp_json_file, 'w') as f:
- WritePrettyJsonFile(bisect_test_contents, f)
-
- revision_to_update = 369411
-
- # Index of the tryjob that is going to have its 'status' value updated.
- tryjob_index = 0
-
- path_to_chroot = '/abs/path/to/chroot'
-
- custom_script = None
-
- update_tryjob_status.UpdateTryjobStatus(
- revision_to_update, update_tryjob_status.TryjobStatus.AUTO,
- temp_json_file, path_to_chroot, custom_script)
-
- # Verify that the tryjob's 'status' has been updated in the status file.
- with open(temp_json_file) as status_file:
- bisect_contents = json.load(status_file)
-
- self.assertEqual(bisect_contents['jobs'][tryjob_index]['status'],
- update_tryjob_status.TryjobStatus.GOOD.value)
-
- mock_get_auto_result.assert_called_once_with(
- path_to_chroot,
- bisect_test_contents['jobs'][tryjob_index]['buildbucket_id'])
-
- mock_find_tryjob_index.assert_called_once()
-
- # Simulate the behavior of `FindTryjobIndex()` when the tryjob exists in the
- # status file.
@mock.patch.object(update_tryjob_status, 'FindTryjobIndex', return_value=0)
- # Simulate the behavior of `GetCustomScriptResult()` when the custom script
- # exit code is in the mapping.
@mock.patch.object(
update_tryjob_status,
'GetCustomScriptResult',
return_value=TryjobStatus.SKIP.value)
- def testSuccessfullyUpdatedTryjobStatusToAuto(
+ def testUpdatedTryjobStatusToAutoPassedWithCustomScript(
self, mock_get_custom_script_result, mock_find_tryjob_index):
bisect_test_contents = {
'start': 369410,
@@ -550,13 +414,11 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# Index of the tryjob that is going to have its 'status' value updated.
tryjob_index = 0
- path_to_chroot = '/abs/path/to/chroot'
-
custom_script_path = '/abs/path/to/custom_script.py'
update_tryjob_status.UpdateTryjobStatus(
revision_to_update, update_tryjob_status.TryjobStatus.CUSTOM_SCRIPT,
- temp_json_file, path_to_chroot, custom_script_path)
+ temp_json_file, custom_script_path)
# Verify that the tryjob's 'status' has been updated in the status file.
with open(temp_json_file) as status_file:
@@ -593,8 +455,6 @@ class UpdateTryjobStatusTest(unittest.TestCase):
revision_to_update = 369411
- path_to_chroot = '/abs/path/to/chroot'
-
nonexistent_update_status = 'revert_status'
custom_script = None
@@ -602,9 +462,9 @@ class UpdateTryjobStatusTest(unittest.TestCase):
# Verify the exception is raised when the `set_status` command line
# argument does not exist in the mapping.
with self.assertRaises(ValueError) as err:
- update_tryjob_status.UpdateTryjobStatus(
- revision_to_update, nonexistent_update_status, temp_json_file,
- path_to_chroot, custom_script)
+ update_tryjob_status.UpdateTryjobStatus(revision_to_update,
+ nonexistent_update_status,
+ temp_json_file, custom_script)
self.assertEqual(
str(err.exception),