blob: 56b556e6641f6a1b0e7d916e9c86b103c8276898 [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 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
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
# 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, 1)
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_llvm_bisection.call_count, 3)
self.assertEqual(mock_traceback.call_count, 3)
self.assertEqual(mock_sleep.call_count, 2)
# 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
assert False, 'Called `llvm_bisection.main()` more than expected.'
# 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.
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()
mock_get_auto_script.assert_called_once()
self.assertEqual(mock_is_file.call_count, 3)
self.assertEqual(mock_llvm_bisection.call_count, 3)
mock_traceback.assert_called_once()
mock_sleep.assert_called_once()
mock_update_tryjobs.assert_called_once()
# 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())
def testFailedToUpdatePendingTryJobs(
self, mock_get_args, mock_get_auto_script, mock_is_file, mock_sleep,
mock_outside_chroot, mock_time, mock_update_tryjobs):
# 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.'
# Use the test function to simulate the behavior of `time.time()`.
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, 1)
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)
mock_sleep.assert_called_once()
self.assertEqual(mock_time.call_count, 3)
self.assertEqual(mock_update_tryjobs.call_count, 2)
if __name__ == '__main__':
unittest.main()