| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # Copyright 2019 The ChromiumOS Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Tests for auto bisection of LLVM.""" |
| |
| |
| import json |
| import os |
| import subprocess |
| import time |
| import traceback |
| import unittest |
| import unittest.mock as mock |
| |
| 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.""" |
| |
| @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) |
| @mock.patch.object( |
| llvm_bisection, |
| "GetCommandLineArgs", |
| return_value=test_helpers.ArgsOutputTest(), |
| ) |
| @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 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, 0) |
| |
| mock_outside_chroot.assert_called_once() |
| mock_get_args.assert_called_once() |
| self.assertEqual(mock_isfile.call_count, 3) |
| self.assertEqual(mock_llvm_bisection.call_count, 3) |
| mock_traceback.assert_called_once() |
| mock_sleep.assert_called_once() |
| |
| @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) |
| @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( |
| llvm_bisection, |
| "GetCommandLineArgs", |
| return_value=test_helpers.ArgsOutputTest(), |
| ) |
| def testFailedToStartBisection( |
| self, |
| mock_get_args, |
| mock_isfile, |
| mock_llvm_bisection, |
| mock_traceback, |
| mock_sleep, |
| mock_outside_chroot, |
| ): |
| |
| mock_isfile.return_value = False |
| mock_llvm_bisection.side_effect = ValueError( |
| "Failed to launch more tryjobs." |
| ) |
| |
| # 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, "Unable to continue bisection.") |
| |
| mock_outside_chroot.assert_called_once() |
| mock_get_args.assert_called_once() |
| self.assertEqual(mock_isfile.call_count, 2) |
| self.assertEqual(mock_llvm_bisection.call_count, 3) |
| self.assertEqual(mock_traceback.call_count, 3) |
| self.assertEqual(mock_sleep.call_count, 2) |
| |
| @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) |
| @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_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 |
| def MockTimePassed(call_count): |
| if call_count < 3: |
| return call_count |
| |
| assert False, "Called `time.time()` more than expected." |
| |
| 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 |
| |
| # Verify the exception is raised when unable to update tryjobs whose |
| # 'status' value is 'pending'. |
| with self.assertRaises(SystemExit) as err: |
| auto_llvm_bisection.main() |
| |
| self.assertEqual( |
| err.exception.code, "Failed to update pending tryjobs." |
| ) |
| |
| mock_outside_chroot.assert_called_once() |
| mock_get_args.assert_called_once() |
| self.assertEqual(mock_isfile.call_count, 2) |
| mock_sleep.assert_called_once() |
| self.assertEqual(mock_time.call_count, 3) |
| |
| @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__": |
| unittest.main() |