blob: 07c0e7155bf3aeedfddfa6f07e21e261554bd905 [file] [log] [blame]
#!/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.
"""Tests for auto bisection of LLVM."""
from __future__ import print_function
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()