chromite: Move Timeout* functions to chromite.lib.timeout_util.
BUG=chromium:323675
CQ-DEPEND=CL:178733
TEST=cbuildbot --buildbot --debug --remote runs on x86-alex-paladin,
x86-mario-paladin, x86-mario-release
TEST=repo grep -E \
'cros_build_lib\.(Timeout|FatalTimeout|TimeoutDecorator|'\
'WaitForCondition|WaitForReturnValue|WaitForTreeStatus|'\
'IsTreeOpen|GetTreeStatus|SubCommandTimeout|Timeout|'\
'WaitForCondition|TreeOpen)'
TEST=all unit tests
Change-Id: Ida5b3b5be04a3b891da0f0c06db832d3d01be972
Reviewed-on: https://chromium-review.googlesource.com/178734
Reviewed-by: Aviv Keshet <akeshet@chromium.org>
Tested-by: David James <davidjames@chromium.org>
Commit-Queue: David James <davidjames@chromium.org>
diff --git a/buildbot/cbuildbot_commands.py b/buildbot/cbuildbot_commands.py
index 723ecba..fc9caaa 100644
--- a/buildbot/cbuildbot_commands.py
+++ b/buildbot/cbuildbot_commands.py
@@ -26,6 +26,7 @@
from chromite.lib import locking
from chromite.lib import osutils
from chromite.lib import parallel
+from chromite.lib import timeout_util
from chromite.scripts import upload_symbols
@@ -1218,10 +1219,10 @@
gs_context = gs.GSContext(acl=acl, dry_run=debug)
try:
- with cros_build_lib.Timeout(timeout):
+ with timeout_util.Timeout(timeout):
gs_context.CopyInto(local_path, upload_url)
- except cros_build_lib.TimeoutError:
- raise cros_build_lib.TimeoutError('Timed out uploading %s' % filename)
+ except timeout_util.TimeoutError:
+ raise timeout_util.TimeoutError('Timed out uploading %s' % filename)
else:
# Update the list of uploaded files.
if update_list:
diff --git a/buildbot/cbuildbot_stages.py b/buildbot/cbuildbot_stages.py
index bed4330..2692b5b 100644
--- a/buildbot/cbuildbot_stages.py
+++ b/buildbot/cbuildbot_stages.py
@@ -47,6 +47,7 @@
from chromite.lib import osutils
from chromite.lib import parallel
from chromite.lib import patch as cros_patch
+from chromite.lib import timeout_util
_FULL_BINHOST = 'FULL_BINHOST'
_PORTAGE_BINHOST = 'PORTAGE_BINHOST'
@@ -273,7 +274,7 @@
commands.UploadArchivedFile(
self.archive_path, self.upload_url, filename, self.debug,
update_list=True, acl=self.acl)
- except (cros_build_lib.RunCommandError, cros_build_lib.TimeoutError) as e:
+ except (cros_build_lib.RunCommandError, timeout_util.TimeoutError) as e:
cros_build_lib.PrintBuildbotStepText('Upload failed')
if strict:
raise
@@ -1571,7 +1572,7 @@
verified by either the Pre-CQ or CQ.
"""
# Submit non-manifest changes if we can.
- if cros_build_lib.IsTreeOpen(
+ if timeout_util.IsTreeOpen(
validation_pool.ValidationPool.STATUS_URL):
pool.SubmitNonManifestChanges(check_tree_open=False)
@@ -2387,7 +2388,7 @@
def PerformStage(self):
if not self.archive_stage.WaitForRecoveryImage():
raise InvalidTestConditionException('Missing recovery image.')
- with cros_build_lib.Timeout(self.SIGNER_TEST_TIMEOUT):
+ with timeout_util.Timeout(self.SIGNER_TEST_TIMEOUT):
commands.RunSignerTests(self._build_root, self._current_board)
@@ -2408,7 +2409,7 @@
extra_env = {}
if self._build_config['useflags']:
extra_env['USE'] = ' '.join(self._build_config['useflags'])
- with cros_build_lib.Timeout(self.UNIT_TEST_TIMEOUT):
+ with timeout_util.Timeout(self.UNIT_TEST_TIMEOUT):
commands.RunUnitTests(self._build_root,
self._current_board,
full=(not self._build_config['quick_unit']),
@@ -2545,7 +2546,7 @@
"""Override and don't set status to FAIL but FORGIVEN instead."""
# Deal with timeout errors specially.
- if isinstance(exception, cros_build_lib.TimeoutError):
+ if isinstance(exception, timeout_util.TimeoutError):
return self._HandleStageTimeoutException(exception)
# 2 for warnings returned by run_suite.py, or CLIENT_HTTP_CODE error
@@ -2581,7 +2582,7 @@
else:
debug = self._options.debug
lab_status.CheckLabStatus(self._current_board)
- with cros_build_lib.Timeout(
+ with timeout_util.Timeout(
self.suite_config.timeout + constants.HWTEST_TIMEOUT_EXTENSION):
commands.RunHWTestSuite(build,
self.suite_config.suite,
diff --git a/buildbot/cbuildbot_stages_unittest.py b/buildbot/cbuildbot_stages_unittest.py
index 99f3ee7..e3209e4 100755
--- a/buildbot/cbuildbot_stages_unittest.py
+++ b/buildbot/cbuildbot_stages_unittest.py
@@ -46,6 +46,7 @@
from chromite.lib import parallel
from chromite.lib import parallel_unittest
from chromite.lib import partial_mock
+from chromite.lib import timeout_util
from chromite.scripts import cbuildbot
# TODO(build): Finish test wrapper (http://crosbug.com/37517).
@@ -725,7 +726,7 @@
# Raise an exception if the user wanted the command to fail.
if timeout:
- m.AndRaise(cros_build_lib.TimeoutError('Timed out'))
+ m.AndRaise(timeout_util.TimeoutError('Timed out'))
cros_build_lib.PrintBuildbotStepFailure()
cros_build_lib.Error(mox.IgnoreArg())
elif returncode != 0:
@@ -1778,7 +1779,7 @@
return_value=committed, autospec=True)
self.PatchObject(gerrit.GerritOnBorgHelper, 'Query',
return_value=my_patches, autospec=True)
- self.PatchObject(cros_build_lib, 'IsTreeOpen', return_value=tree_open,
+ self.PatchObject(timeout_util, 'IsTreeOpen', return_value=tree_open,
autospec=True)
exit_it = itertools.chain([False] * runs, itertools.repeat(True))
self.PatchObject(validation_pool.ValidationPool, 'ShouldExitEarly',
diff --git a/buildbot/cbuildbot_unittest.py b/buildbot/cbuildbot_unittest.py
index 60b4c47..3491b4e 100755
--- a/buildbot/cbuildbot_unittest.py
+++ b/buildbot/cbuildbot_unittest.py
@@ -22,6 +22,7 @@
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.lib import parallel_unittest
+from chromite.lib import timeout_util
from chromite.scripts import cbuildbot
# TODO(build): Finish test wrapper (http://crosbug.com/37517).
@@ -277,7 +278,7 @@
# Let this test run for a max of 30s; if it takes longer, then it's
# likely that there is an exec loop in the pathways.
- @cros_build_lib.TimeoutDecorator(30)
+ @timeout_util.TimeoutDecorator(30)
def testDepotTools(self):
"""Test that the entry point used by depot_tools works."""
path = os.path.join(constants.SOURCE_ROOT, 'chromite', 'buildbot',
diff --git a/buildbot/validation_pool.py b/buildbot/validation_pool.py
index 4dff12e..5069074 100644
--- a/buildbot/validation_pool.py
+++ b/buildbot/validation_pool.py
@@ -31,6 +31,7 @@
from chromite.lib import gob_util
from chromite.lib import gs
from chromite.lib import patch as cros_patch
+from chromite.lib import timeout_util
# Third-party libraries bundled with chromite need to be listed after the
# first chromite import.
@@ -1052,7 +1053,7 @@
# TimeoutErrors are often flaky.
exc = self.tracebacks[0].exception
if (isinstance(exc, results_lib.StepFailure) and exc.possibly_flaky or
- isinstance(exc, cros_build_lib.TimeoutError)):
+ isinstance(exc, timeout_util.TimeoutError)):
flaky = True
return flaky
@@ -1419,7 +1420,7 @@
time_left = end_time - time.time()
# Wait until the tree opens.
- if check_tree_open and not cros_build_lib.IsTreeOpen(
+ if check_tree_open and not timeout_util.IsTreeOpen(
cls.STATUS_URL, cls.SLEEP_TIMEOUT, timeout=time_left,
throttled_ok=throttled_ok):
raise TreeIsClosedException(closed_or_throttled=not throttled_ok)
@@ -1792,9 +1793,9 @@
dry_run=self.dryrun)
if (check_tree_open and not self.dryrun and not
- cros_build_lib.IsTreeOpen(self.STATUS_URL, self.SLEEP_TIMEOUT,
- timeout=self.MAX_TIMEOUT,
- throttled_ok=throttled_ok)):
+ timeout_util.IsTreeOpen(self.STATUS_URL, self.SLEEP_TIMEOUT,
+ timeout=self.MAX_TIMEOUT,
+ throttled_ok=throttled_ok)):
raise TreeIsClosedException(close_or_throttled=not throttled_ok)
# First, reload all of the changes from the Gerrit server so that we have a
diff --git a/buildbot/validation_pool_unittest.py b/buildbot/validation_pool_unittest.py
index c4a2721..e6e65db 100755
--- a/buildbot/validation_pool_unittest.py
+++ b/buildbot/validation_pool_unittest.py
@@ -34,6 +34,7 @@
from chromite.lib import partial_mock
from chromite.lib import patch as cros_patch
from chromite.lib import patch_unittest
+from chromite.lib import timeout_util
import mock
@@ -90,7 +91,7 @@
self.build_root = 'fakebuildroot'
self.PatchObject(gob_util, 'CreateHttpConn',
side_effect=AssertionError('Test should not contact GoB'))
- self.PatchObject(cros_build_lib, 'IsTreeOpen', return_value=True)
+ self.PatchObject(timeout_util, 'IsTreeOpen', return_value=True)
def MockPatch(self, change_id=None, patch_number=None, is_merged=False,
project='chromiumos/chromite', remote=constants.EXTERNAL_REMOTE,
diff --git a/lib/cros_build_lib.py b/lib/cros_build_lib.py
index 55dcb48..0d46aec 100644
--- a/lib/cros_build_lib.py
+++ b/lib/cros_build_lib.py
@@ -9,7 +9,6 @@
from email.utils import formatdate
import errno
import functools
-import json
import logging
import os
import re
@@ -19,7 +18,6 @@
import sys
import tempfile
import time
-import urllib
# TODO(build): Fix this.
# This should be absolute import, but that requires fixing all
@@ -1011,137 +1009,6 @@
return NoOpContextManager()
-class TimeoutError(Exception):
- """Raises when code within Timeout has been run too long."""
-
-
-@contextlib.contextmanager
-def Timeout(max_run_time):
- """ContextManager that alarms if code is ran for too long.
-
- Timeout can run nested and raises a TimeoutException if the timeout
- is reached. Timeout can also nest underneath FatalTimeout.
-
- Args:
- max_run_time: Number (integer) of seconds to wait before sending SIGALRM.
- """
- max_run_time = int(max_run_time)
- if max_run_time <= 0:
- raise ValueError("max_run_time must be greater than zero")
-
- # pylint: disable=W0613
- def kill_us(sig_num, frame):
- raise TimeoutError("Timeout occurred- waited %s seconds." % max_run_time)
-
- original_handler = signal.signal(signal.SIGALRM, kill_us)
- previous_time = int(time.time())
-
- # Signal the min in case the leftover time was smaller than this timeout.
- remaining_timeout = signal.alarm(0)
- if remaining_timeout:
- signal.alarm(min(remaining_timeout, max_run_time))
- else:
- signal.alarm(max_run_time)
-
- try:
- yield
- finally:
- # Cancel the alarm request and restore the original handler.
- signal.alarm(0)
- signal.signal(signal.SIGALRM, original_handler)
-
- # Ensure the previous handler will fire if it was meant to.
- if remaining_timeout > 0:
- # Signal the previous handler if it would have already passed.
- time_left = remaining_timeout - (int(time.time()) - previous_time)
- if time_left <= 0:
- signals.RelaySignal(original_handler, signal.SIGALRM, None)
- else:
- signal.alarm(time_left)
-
-
-@contextlib.contextmanager
-def FatalTimeout(max_run_time):
- """ContextManager that exits the program if code is run for too long.
-
- This implementation is fairly simple, thus multiple timeouts
- cannot be active at the same time.
-
- Additionally, if the timeout has elapsed, it'll trigger a SystemExit
- exception within the invoking code, ultimately propagating that past
- itself. If the underlying code tries to suppress the SystemExit, once
- a minute it'll retrigger SystemExit until control is returned to this
- manager.
-
- Args:
- max_run_time: a positive integer.
- """
- max_run_time = int(max_run_time)
- if max_run_time <= 0:
- raise ValueError("max_run_time must be greater than zero")
-
- # pylint: disable=W0613
- def kill_us(sig_num, frame):
- # While this SystemExit *should* crash it's way back up the
- # stack to our exit handler, we do have live/production code
- # that uses blanket except statements which could suppress this.
- # As such, keep scheduling alarms until our exit handler runs.
- # Note that there is a potential conflict via this code, and
- # RunCommand's kill_timeout; thus we set the alarming interval
- # fairly high.
- signal.alarm(60)
- raise SystemExit("Timeout occurred- waited %i seconds, failing."
- % max_run_time)
-
- original_handler = signal.signal(signal.SIGALRM, kill_us)
- remaining_timeout = signal.alarm(max_run_time)
- if remaining_timeout:
- # Restore things to the way they were.
- signal.signal(signal.SIGALRM, original_handler)
- signal.alarm(remaining_timeout)
- # ... and now complain. Unfortunately we can't easily detect this
- # upfront, thus the reset dance above.
- raise Exception("_Timeout cannot be used in parallel to other alarm "
- "handling code; failing")
- try:
- yield
- finally:
- # Cancel the alarm request and restore the original handler.
- signal.alarm(0)
- signal.signal(signal.SIGALRM, original_handler)
-
-
-def TimeoutDecorator(max_time):
- """Decorator used to ensure a func is interrupted if it's running too long."""
- # Save off the built-in versions of time.time, signal.signal, and
- # signal.alarm, in case they get mocked out later. We want to ensure that
- # tests don't accidentally mock out the functions used by Timeout.
- def _Save():
- return time.time, signal.signal, signal.alarm
- def _Restore(values):
- (time.time, signal.signal, signal.alarm) = values
- builtins = _Save()
-
- def NestedTimeoutDecorator(func):
- @functools.wraps(func)
- def TimeoutWrapper(*args, **kwargs):
- new = _Save()
- try:
- _Restore(builtins)
- with Timeout(max_time):
- _Restore(new)
- try:
- func(*args, **kwargs)
- finally:
- _Restore(builtins)
- finally:
- _Restore(new)
-
- return TimeoutWrapper
-
- return NestedTimeoutDecorator
-
-
class ContextManagerStack(object):
"""Context manager that is designed to safely allow nesting and stacking.
@@ -1220,64 +1087,6 @@
raise exc_type, exc, traceback
-def WaitForCondition(*args, **kwargs):
- """Periodically run a function, waiting in between runs.
-
- Continues to run until the function returns True.
-
- Args:
- See WaitForReturnValue([True], ...)
-
- Raises:
- TimeoutError when the timeout is exceeded.
- """
- WaitForReturnValue([True], *args, **kwargs)
-
-
-def WaitForReturnValue(values, func, timeout, period=1, side_effect_func=None,
- func_args=None, func_kwargs=None):
- """Periodically run a function, waiting in between runs.
-
- Continues to run until the function return value is in the list
- of accepted |values|.
-
- Args:
- values: A list or set of acceptable return values from |func|.
- func: The function to run to test for a value.
- timeout: The maximum amount of time to wait, in integer seconds. Minimum
- value: 1.
- period: Integer number of seconds between calls to |func|. Default: 1
- side_effect_func: Optional function to be called between polls of func,
- typically to output logging messages.
- func_args: Optional list of positional arguments to be passed to |func|.
- func_kwargs: Optional dictionary of keyword arguments to be passed to
- |func|.
-
- Returns:
- The value most recently returned by |func|.
-
- Raises:
- TimeoutError when the timeout is exceeded.
- """
- assert period >= 0
- # Timeout requires timeout >= 1.
- assert timeout >= 1
- func_args = func_args or []
- func_kwargs = func_kwargs or {}
- with Timeout(timeout):
- while True:
- timestamp = time.time()
- value = func(*func_args, **func_kwargs)
- if value in values:
- return value
-
- time_remaining = period - int(time.time() - timestamp)
- if time_remaining > 0:
- if side_effect_func:
- side_effect_func()
- time.sleep(time_remaining)
-
-
def RunCurl(args, **kwargs):
"""Runs curl and wraps around all necessary hacks."""
cmd = ['curl']
@@ -1437,114 +1246,6 @@
return trues, falses
-def _GetStatus(status_url):
- """Polls |status_url| and returns the retrieved tree status.
-
- This function gets a JSON response from |status_url|, and returns the
- value associated with the 'general_state' key, if one exists and the
- http request was successful.
-
- Returns:
- The tree status, as a string, if it was successfully retrieved. Otherwise
- None.
- """
- try:
- # Check for successful response code.
- response = urllib.urlopen(status_url)
- if response.getcode() == 200:
- data = json.load(response)
- if data.has_key('general_state'):
- return data['general_state']
- # We remain robust against IOError's.
- except IOError as e:
- logging.error('Could not reach %s: %r', status_url, e)
-
-
-def WaitForTreeStatus(status_url, period=1, timeout=1, throttled_ok=False):
- """Wait for tree status to be open (or throttled, if |throttled_ok|).
-
- Args:
- status_url: The status url to check i.e.
- 'https://status.appspot.com/current?format=json'
- polling_period: Time in seconds to wait between polling
-
- Returns:
- The most recent tree status, either constants.TREE_OPEN or
- constants.TREE_THROTTLED (if |throttled_ok|)
-
- Raises:
- TimeoutError if timeout expired before tree reached acceptable status.
- """
- acceptable_states = set([constants.TREE_OPEN])
- verb = 'open'
- if throttled_ok:
- acceptable_states.add(constants.TREE_THROTTLED)
- verb = 'not be closed'
-
- timeout = max(timeout, 1)
-
- end_time = time.time() + timeout
-
- def _LogMessage():
- time_left = end_time - time.time()
- Info('Waiting for the tree to %s (%d minutes left)...', verb,
- time_left / 60)
-
- def _get_status():
- return _GetStatus(status_url)
-
- return WaitForReturnValue(acceptable_states, _get_status, timeout=timeout,
- period=period, side_effect_func=_LogMessage)
-
-
-def IsTreeOpen(status_url, period=1, timeout=1, throttled_ok=False):
- """Wait for tree status to be open (or throttled, if |throttled_ok|).
-
- Args:
- status_url: The status url to check i.e.
- 'https://status.appspot.com/current?format=json'
- polling_period: Time in seconds to wait between polling
-
- Returns:
- True if the tree is open (or throttled, if |throttled_ok|). False if
- timeout expired before tree reached acceptable status.
- """
- try:
- WaitForTreeStatus(status_url, period, timeout, throttled_ok)
- except TimeoutError:
- return False
- return True
-
-
-def GetTreeStatus(status_url, polling_period=0, timeout=0):
- """Returns the current tree status as fetched from |status_url|.
-
- This function returns the tree status as a string, either
- constants.TREE_OPEN, constants.TREE_THROTTLED, or constants.TREE_CLOSED.
-
- Args:
- status_url: The status url to check i.e.
- 'https://status.appspot.com/current?format=json'
- polling_period: Time to wait in seconds between polling attempts.
- timeout: Maximum time in seconds to wait for status.
-
- Returns:
- constants.TREE_OPEN, constants.TREE_THROTTLED, or constants.TREE_CLOSED
-
- Raises:
- TimeoutError if the timeout expired before the status could be successfully
- fetched.
- """
- acceptable_states = set([constants.TREE_OPEN, constants.TREE_THROTTLED,
- constants.TREE_CLOSED])
-
- def _get_status():
- return _GetStatus(status_url)
-
- return WaitForReturnValue(acceptable_states, _get_status, timeout,
- polling_period)
-
-
@contextlib.contextmanager
def _Open(input):
"""Convenience ctx that accepts a file path or an already open file object."""
diff --git a/lib/cros_build_lib_unittest.py b/lib/cros_build_lib_unittest.py
index 8d49f5d..2a391e4 100755
--- a/lib/cros_build_lib_unittest.py
+++ b/lib/cros_build_lib_unittest.py
@@ -19,7 +19,6 @@
import signal
import StringIO
import time
-import urllib
import __builtin__
from chromite.buildbot import constants
@@ -735,33 +734,6 @@
self.assertFalse(cros_build_lib.BooleanShellValue(v, False))
-class TestTimeouts(cros_test_lib.TestCase):
-
- def testTimeout(self):
- """Tests that we can nest Timeout correctly."""
- self.assertFalse('mock' in str(time.sleep).lower())
- with cros_build_lib.Timeout(30):
- with cros_build_lib.Timeout(20):
- with cros_build_lib.Timeout(1):
- self.assertRaises(cros_build_lib.TimeoutError, time.sleep, 10)
-
- # Should not raise a timeout exception as 20 > 2.
- time.sleep(1)
-
- def testTimeoutNested(self):
- """Tests that we still re-raise an alarm if both are reached."""
- with cros_build_lib.Timeout(1):
- try:
- with cros_build_lib.Timeout(2):
- self.assertRaises(cros_build_lib.TimeoutError, time.sleep, 1)
-
- # Craziness to catch nested timeouts.
- except cros_build_lib.TimeoutError:
- pass
- else:
- self.assertTrue(False, 'Should have thrown an exception')
-
-
class TestContextManagerStack(cros_test_lib.TestCase):
def test(self):
@@ -915,136 +887,6 @@
self.assertEqual(branches, ['refs/remotes/origin/release-R23-2913.B'])
-# pylint: disable=W0212,R0904
-class TestTreeStatus(cros_test_lib.MoxTestCase):
- """Tests TreeStatus method in cros_build_lib."""
-
- status_url = 'https://chromiumos-status.appspot.com/current?format=json'
-
- def setUp(self):
- pass
-
- def _TreeStatusFile(self, message, general_state):
- """Returns a file-like object with the status message writtin in it."""
- my_response = self.mox.CreateMockAnything()
- my_response.json = '{"message": "%s", "general_state": "%s"}' % (
- message, general_state)
- return my_response
-
- def _SetupMockTreeStatusResponses(self, status_url,
- final_tree_status='Tree is open.',
- final_general_state=constants.TREE_OPEN,
- rejected_tree_status='Tree is closed.',
- rejected_general_state=
- constants.TREE_CLOSED,
- rejected_status_count=0,
- retries_500=0,
- output_final_status=True):
- """Mocks out urllib.urlopen commands to simulate a given tree status.
-
- Args:
-
- status_url: The status url that status will be fetched from.
- final_tree_status: The final value of tree status that will be returned
- by urlopen.
- final_general_state: The final value of 'general_state' that will be
- returned by urlopen.
- rejected_tree_status: An intermediate value of tree status that will be
- returned by urlopen and retried upon.
- rejected_general_state: An intermediate value of 'general_state' that
- will be returned by urlopen and retried upon.
- rejected_status_count: The number of times urlopen will return the
- rejected state.
- retries_500: The number of times urlopen will fail with a 500 code.
- output_final_status: If True, the status given by final_tree_status and
- final_general_state will be the last status returned by urlopen. If
- False, final_tree_status will never be returned, and instead an
- unlimited number of times rejected_response will be returned.
- """
-
- final_response = self._TreeStatusFile(final_tree_status,
- final_general_state)
- rejected_response = self._TreeStatusFile(rejected_tree_status,
- rejected_general_state)
- error_500_response = self.mox.CreateMockAnything()
- self.mox.StubOutWithMock(urllib, 'urlopen')
-
- for _ in range(retries_500):
- urllib.urlopen(status_url).AndReturn(error_500_response)
- error_500_response.getcode().AndReturn(500)
-
- if output_final_status:
- for _ in range(rejected_status_count):
- urllib.urlopen(status_url).AndReturn(rejected_response)
- rejected_response.getcode().AndReturn(200)
- rejected_response.read().AndReturn(rejected_response.json)
-
- urllib.urlopen(status_url).AndReturn(final_response)
- final_response.getcode().AndReturn(200)
- final_response.read().AndReturn(final_response.json)
- else:
- urllib.urlopen(status_url).MultipleTimes().AndReturn(rejected_response)
- rejected_response.getcode().MultipleTimes().AndReturn(200)
- rejected_response.read().MultipleTimes().AndReturn(
- rejected_response.json)
-
- self.mox.ReplayAll()
-
- def testTreeIsOpen(self):
- """Tests that we return True is the tree is open."""
- self._SetupMockTreeStatusResponses(self.status_url,
- rejected_status_count=5,
- retries_500=5)
- self.assertTrue(cros_build_lib.IsTreeOpen(self.status_url,
- period=0))
-
- def testTreeIsClosed(self):
- """Tests that we return false is the tree is closed."""
- self._SetupMockTreeStatusResponses(self.status_url,
- output_final_status=False)
- self.assertFalse(cros_build_lib.IsTreeOpen(self.status_url,
- period=0.1))
-
- def testTreeIsThrottled(self):
- """Tests that we return True if the tree is throttled."""
- self._SetupMockTreeStatusResponses(self.status_url,
- 'Tree is throttled (flaky bug on flaky builder)',
- constants.TREE_THROTTLED)
- self.assertTrue(cros_build_lib.IsTreeOpen(self.status_url,
- throttled_ok=True))
-
- def testTreeIsThrottledNotOk(self):
- """Tests that we respect throttled_ok"""
- self._SetupMockTreeStatusResponses(self.status_url,
- rejected_tree_status='Tree is throttled (flaky bug on flaky builder)',
- rejected_general_state=constants.TREE_THROTTLED,
- output_final_status=False)
- self.assertFalse(cros_build_lib.IsTreeOpen(self.status_url,
- period=0.1))
-
- def testWaitForStatusOpen(self):
- """Tests that we can wait for a tree open response."""
- self._SetupMockTreeStatusResponses(self.status_url)
- self.assertEqual(cros_build_lib.WaitForTreeStatus(self.status_url),
- constants.TREE_OPEN)
-
-
- def testWaitForStatusThrottled(self):
- """Tests that we can wait for a tree open response."""
- self._SetupMockTreeStatusResponses(self.status_url,
- final_general_state=constants.TREE_THROTTLED)
- self.assertEqual(cros_build_lib.WaitForTreeStatus(self.status_url,
- throttled_ok=True),
- constants.TREE_THROTTLED)
-
- def testWaitForStatusFailure(self):
- """Tests that we can wait for a tree open response."""
- self._SetupMockTreeStatusResponses(self.status_url,
- output_final_status=False)
- self.assertRaises(cros_build_lib.TimeoutError,
- cros_build_lib.WaitForTreeStatus, self.status_url,
- period=0.1)
-
class Test_iflatten_instance(cros_test_lib.TestCase):
def test_it(self):
diff --git a/lib/cros_test_lib.py b/lib/cros_test_lib.py
index 729e0bb..11ba983 100644
--- a/lib/cros_test_lib.py
+++ b/lib/cros_test_lib.py
@@ -34,6 +34,7 @@
import gob_util
import osutils
import terminal
+import timeout_util
if 'chromite' not in sys.modules:
# TODO(build): Finish test wrapper (http://crosbug.com/37517).
@@ -218,7 +219,7 @@
if timeout is not None:
for name, func in scope.iteritems():
if name.startswith('test') and hasattr(func, '__call__'):
- wrapper = cros_build_lib.TimeoutDecorator(timeout)
+ wrapper = timeout_util.TimeoutDecorator(timeout)
scope[name] = wrapper(func)
return type.__new__(mcs, name, bases, scope)
diff --git a/lib/cros_test_lib_unittest.py b/lib/cros_test_lib_unittest.py
index c5ff28f..093c063 100755
--- a/lib/cros_test_lib_unittest.py
+++ b/lib/cros_test_lib_unittest.py
@@ -12,10 +12,10 @@
sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
'..', '..'))
-from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import cros_build_lib_unittest
from chromite.lib import partial_mock
+from chromite.lib import timeout_util
# TODO(build): Finish test wrapper (http://crosbug.com/37517).
# Until then, this has to be after the chromite imports.
@@ -196,7 +196,7 @@
# Run the test case, verifying it raises a TimeoutError.
test = TimeoutTestCase(methodName='testSleeping')
- self.assertRaises(cros_build_lib.TimeoutError, test.testSleeping)
+ self.assertRaises(timeout_util.TimeoutError, test.testSleeping)
if __name__ == '__main__':
diff --git a/lib/parallel.py b/lib/parallel.py
index e4160c0..075f04a 100644
--- a/lib/parallel.py
+++ b/lib/parallel.py
@@ -27,6 +27,7 @@
from chromite.buildbot import cbuildbot_results as results_lib
from chromite.lib import cros_build_lib
from chromite.lib import osutils
+from chromite.lib import timeout_util
_BUFSIZE = 1024
@@ -304,7 +305,7 @@
error = str(ex)
possibly_flaky = ex.possibly_flaky
except BaseException as ex:
- possibly_flaky = isinstance(ex, cros_build_lib.TimeoutError)
+ possibly_flaky = isinstance(ex, timeout_util.TimeoutError)
error = traceback.format_exc()
if self._killing.is_set():
traceback.print_exc()
diff --git a/lib/remote_access.py b/lib/remote_access.py
index 8ffd748..fc232ff 100644
--- a/lib/remote_access.py
+++ b/lib/remote_access.py
@@ -12,6 +12,7 @@
import time
from chromite.lib import cros_build_lib
+from chromite.lib import timeout_util
_path = os.path.dirname(os.path.realpath(__file__))
@@ -174,9 +175,9 @@
self.RemoteSh('touch %s && reboot' % REBOOT_MARKER)
time.sleep(CHECK_INTERVAL)
try:
- cros_build_lib.WaitForCondition(self._CheckIfRebooted, CHECK_INTERVAL,
- REBOOT_MAX_WAIT)
- except cros_build_lib.TimeoutError:
+ timeout_util.WaitForCondition(self._CheckIfRebooted, CHECK_INTERVAL,
+ REBOOT_MAX_WAIT)
+ except timeout_util.TimeoutError:
cros_build_lib.Die('Reboot has not completed after %s seconds; giving up.'
% (REBOOT_MAX_WAIT,))
diff --git a/lib/stats.py b/lib/stats.py
index ad2d2fc..63a5f4f 100644
--- a/lib/stats.py
+++ b/lib/stats.py
@@ -16,6 +16,7 @@
from chromite.lib import cros_build_lib
from chromite.lib import git
from chromite.lib import osutils
+from chromite.lib import timeout_util
class Stats(object):
@@ -158,11 +159,11 @@
if not cls._UploadConditionsMet(stats):
return
- with cros_build_lib.Timeout(timeout):
+ with timeout_util.Timeout(timeout):
try:
cls._Upload(stats, url)
# Stats upload errors are silenced, for the sake of user experience.
- except cros_build_lib.TimeoutError:
+ except timeout_util.TimeoutError:
logging.debug(cls.TIMEOUT_ERROR, timeout)
except urllib2.HTTPError as e:
# HTTPError has a geturl() method, but it relies on self.url, which
diff --git a/lib/stats_unittest.py b/lib/stats_unittest.py
index 5df8cfb..40d0b6f 100755
--- a/lib/stats_unittest.py
+++ b/lib/stats_unittest.py
@@ -11,12 +11,12 @@
import urllib2
sys.path.insert(0, os.path.abspath('%s/../..' % os.path.dirname(__file__)))
-from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import parallel
from chromite.lib import parallel_unittest
from chromite.lib import partial_mock
from chromite.lib import stats
+from chromite.lib import timeout_util
# pylint: disable=W0212
@@ -161,7 +161,7 @@
def testUploadTimeoutIgnore(self):
"""We don't propagate timeouts during upload."""
self.CheckSuppressException(
- cros_build_lib.TimeoutError(),
+ timeout_util.TimeoutError(),
stats.StatsUploader.TIMEOUT_ERROR
% (stats.StatsUploader.UPLOAD_TIMEOUT,))
diff --git a/lib/timeout_util.py b/lib/timeout_util.py
new file mode 100644
index 0000000..d044251
--- /dev/null
+++ b/lib/timeout_util.py
@@ -0,0 +1,315 @@
+# Copyright (c) 2013 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.
+
+"""Functions for implementing timeouts."""
+
+
+import contextlib
+import functools
+import logging
+import json
+import signal
+import time
+import urllib
+
+from chromite.buildbot import constants
+from chromite.lib import signals
+
+
+class TimeoutError(Exception):
+ """Raises when code within Timeout has been run too long."""
+
+
+@contextlib.contextmanager
+def Timeout(max_run_time):
+ """ContextManager that alarms if code is ran for too long.
+
+ Timeout can run nested and raises a TimeoutException if the timeout
+ is reached. Timeout can also nest underneath FatalTimeout.
+
+ Args:
+ max_run_time: Number (integer) of seconds to wait before sending SIGALRM.
+ """
+ max_run_time = int(max_run_time)
+ if max_run_time <= 0:
+ raise ValueError("max_run_time must be greater than zero")
+
+ # pylint: disable=W0613
+ def kill_us(sig_num, frame):
+ raise TimeoutError("Timeout occurred- waited %s seconds." % max_run_time)
+
+ original_handler = signal.signal(signal.SIGALRM, kill_us)
+ previous_time = int(time.time())
+
+ # Signal the min in case the leftover time was smaller than this timeout.
+ remaining_timeout = signal.alarm(0)
+ if remaining_timeout:
+ signal.alarm(min(remaining_timeout, max_run_time))
+ else:
+ signal.alarm(max_run_time)
+
+ try:
+ yield
+ finally:
+ # Cancel the alarm request and restore the original handler.
+ signal.alarm(0)
+ signal.signal(signal.SIGALRM, original_handler)
+
+ # Ensure the previous handler will fire if it was meant to.
+ if remaining_timeout > 0:
+ # Signal the previous handler if it would have already passed.
+ time_left = remaining_timeout - (int(time.time()) - previous_time)
+ if time_left <= 0:
+ signals.RelaySignal(original_handler, signal.SIGALRM, None)
+ else:
+ signal.alarm(time_left)
+
+
+@contextlib.contextmanager
+def FatalTimeout(max_run_time):
+ """ContextManager that exits the program if code is run for too long.
+
+ This implementation is fairly simple, thus multiple timeouts
+ cannot be active at the same time.
+
+ Additionally, if the timeout has elapsed, it'll trigger a SystemExit
+ exception within the invoking code, ultimately propagating that past
+ itself. If the underlying code tries to suppress the SystemExit, once
+ a minute it'll retrigger SystemExit until control is returned to this
+ manager.
+
+ Args:
+ max_run_time: a positive integer.
+ """
+ max_run_time = int(max_run_time)
+ if max_run_time <= 0:
+ raise ValueError("max_run_time must be greater than zero")
+
+ # pylint: disable=W0613
+ def kill_us(sig_num, frame):
+ # While this SystemExit *should* crash it's way back up the
+ # stack to our exit handler, we do have live/production code
+ # that uses blanket except statements which could suppress this.
+ # As such, keep scheduling alarms until our exit handler runs.
+ # Note that there is a potential conflict via this code, and
+ # RunCommand's kill_timeout; thus we set the alarming interval
+ # fairly high.
+ signal.alarm(60)
+ raise SystemExit("Timeout occurred- waited %i seconds, failing."
+ % max_run_time)
+
+ original_handler = signal.signal(signal.SIGALRM, kill_us)
+ remaining_timeout = signal.alarm(max_run_time)
+ if remaining_timeout:
+ # Restore things to the way they were.
+ signal.signal(signal.SIGALRM, original_handler)
+ signal.alarm(remaining_timeout)
+ # ... and now complain. Unfortunately we can't easily detect this
+ # upfront, thus the reset dance above.
+ raise Exception("_Timeout cannot be used in parallel to other alarm "
+ "handling code; failing")
+ try:
+ yield
+ finally:
+ # Cancel the alarm request and restore the original handler.
+ signal.alarm(0)
+ signal.signal(signal.SIGALRM, original_handler)
+
+
+def TimeoutDecorator(max_time):
+ """Decorator used to ensure a func is interrupted if it's running too long."""
+ # Save off the built-in versions of time.time, signal.signal, and
+ # signal.alarm, in case they get mocked out later. We want to ensure that
+ # tests don't accidentally mock out the functions used by Timeout.
+ def _Save():
+ return time.time, signal.signal, signal.alarm
+ def _Restore(values):
+ (time.time, signal.signal, signal.alarm) = values
+ builtins = _Save()
+
+ def NestedTimeoutDecorator(func):
+ @functools.wraps(func)
+ def TimeoutWrapper(*args, **kwargs):
+ new = _Save()
+ try:
+ _Restore(builtins)
+ with Timeout(max_time):
+ _Restore(new)
+ try:
+ func(*args, **kwargs)
+ finally:
+ _Restore(builtins)
+ finally:
+ _Restore(new)
+
+ return TimeoutWrapper
+
+ return NestedTimeoutDecorator
+
+
+def WaitForCondition(*args, **kwargs):
+ """Periodically run a function, waiting in between runs.
+
+ Continues to run until the function returns True.
+
+ Args:
+ See WaitForReturnValue([True], ...)
+
+ Raises:
+ TimeoutError when the timeout is exceeded.
+ """
+ WaitForReturnValue([True], *args, **kwargs)
+
+
+def WaitForReturnValue(values, func, timeout, period=1, side_effect_func=None,
+ func_args=None, func_kwargs=None):
+ """Periodically run a function, waiting in between runs.
+
+ Continues to run until the function return value is in the list
+ of accepted |values|.
+
+ Args:
+ values: A list or set of acceptable return values from |func|.
+ func: The function to run to test for a value.
+ timeout: The maximum amount of time to wait, in integer seconds. Minimum
+ value: 1.
+ period: Integer number of seconds between calls to |func|. Default: 1
+ side_effect_func: Optional function to be called between polls of func,
+ typically to output logging messages.
+ func_args: Optional list of positional arguments to be passed to |func|.
+ func_kwargs: Optional dictionary of keyword arguments to be passed to
+ |func|.
+
+ Returns:
+ The value most recently returned by |func|.
+
+ Raises:
+ TimeoutError when the timeout is exceeded.
+ """
+ assert period >= 0
+ # Timeout requires timeout >= 1.
+ assert timeout >= 1
+ func_args = func_args or []
+ func_kwargs = func_kwargs or {}
+ with Timeout(timeout):
+ while True:
+ timestamp = time.time()
+ value = func(*func_args, **func_kwargs)
+ if value in values:
+ return value
+
+ time_remaining = period - int(time.time() - timestamp)
+ if time_remaining > 0:
+ if side_effect_func:
+ side_effect_func()
+ time.sleep(time_remaining)
+
+
+def _GetStatus(status_url):
+ """Polls |status_url| and returns the retrieved tree status.
+
+ This function gets a JSON response from |status_url|, and returns the
+ value associated with the 'general_state' key, if one exists and the
+ http request was successful.
+
+ Returns:
+ The tree status, as a string, if it was successfully retrieved. Otherwise
+ None.
+ """
+ try:
+ # Check for successful response code.
+ response = urllib.urlopen(status_url)
+ if response.getcode() == 200:
+ data = json.load(response)
+ if data.has_key('general_state'):
+ return data['general_state']
+ # We remain robust against IOError's.
+ except IOError as e:
+ logging.error('Could not reach %s: %r', status_url, e)
+
+
+def WaitForTreeStatus(status_url, period=1, timeout=1, throttled_ok=False):
+ """Wait for tree status to be open (or throttled, if |throttled_ok|).
+
+ Args:
+ status_url: The status url to check i.e.
+ 'https://status.appspot.com/current?format=json'
+ polling_period: Time in seconds to wait between polling
+
+ Returns:
+ The most recent tree status, either constants.TREE_OPEN or
+ constants.TREE_THROTTLED (if |throttled_ok|)
+
+ Raises:
+ TimeoutError if timeout expired before tree reached acceptable status.
+ """
+ acceptable_states = set([constants.TREE_OPEN])
+ verb = 'open'
+ if throttled_ok:
+ acceptable_states.add(constants.TREE_THROTTLED)
+ verb = 'not be closed'
+
+ timeout = max(timeout, 1)
+
+ end_time = time.time() + timeout
+
+ def _LogMessage():
+ time_left = end_time - time.time()
+ logging.info('Waiting for the tree to %s (%d minutes left)...', verb,
+ time_left / 60)
+
+ def _get_status():
+ return _GetStatus(status_url)
+
+ return WaitForReturnValue(acceptable_states, _get_status, timeout=timeout,
+ period=period, side_effect_func=_LogMessage)
+
+
+
+def IsTreeOpen(status_url, period=1, timeout=1, throttled_ok=False):
+ """Wait for tree status to be open (or throttled, if |throttled_ok|).
+
+ Args:
+ status_url: The status url to check i.e.
+ 'https://status.appspot.com/current?format=json'
+ polling_period: Time in seconds to wait between polling
+
+ Returns:
+ True if the tree is open (or throttled, if |throttled_ok|). False if
+ timeout expired before tree reached acceptable status.
+ """
+ try:
+ WaitForTreeStatus(status_url, period, timeout, throttled_ok)
+ except TimeoutError:
+ return False
+ return True
+
+
+def GetTreeStatus(status_url, polling_period=0, timeout=0):
+ """Returns the current tree status as fetched from |status_url|.
+
+ This function returns the tree status as a string, either
+ constants.TREE_OPEN, constants.TREE_THROTTLED, or constants.TREE_CLOSED.
+
+ Args:
+ status_url: The status url to check i.e.
+ 'https://status.appspot.com/current?format=json'
+ polling_period: Time to wait in seconds between polling attempts.
+ timeout: Maximum time in seconds to wait for status.
+
+ Returns:
+ constants.TREE_OPEN, constants.TREE_THROTTLED, or constants.TREE_CLOSED
+
+ Raises:
+ TimeoutError if the timeout expired before the status could be successfully
+ fetched.
+ """
+ acceptable_states = set([constants.TREE_OPEN, constants.TREE_THROTTLED,
+ constants.TREE_CLOSED])
+
+ def _get_status():
+ return _GetStatus(status_url)
+
+ return WaitForReturnValue(acceptable_states, _get_status, timeout,
+ polling_period)
diff --git a/lib/timeout_util_unittest.py b/lib/timeout_util_unittest.py
new file mode 100755
index 0000000..e5e0888
--- /dev/null
+++ b/lib/timeout_util_unittest.py
@@ -0,0 +1,182 @@
+#!/usr/bin/python
+# Copyright (c) 2013 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.
+
+"""Test suite for timeout_util.py"""
+
+import os
+import sys
+import time
+import urllib
+
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
+ os.path.abspath(__file__)))))
+
+from chromite.buildbot import constants
+from chromite.lib import cros_test_lib
+from chromite.lib import timeout_util
+
+
+# pylint: disable=W0212,R0904
+
+
+class TestTimeouts(cros_test_lib.TestCase):
+
+ def testTimeout(self):
+ """Tests that we can nest Timeout correctly."""
+ self.assertFalse('mock' in str(time.sleep).lower())
+ with timeout_util.Timeout(30):
+ with timeout_util.Timeout(20):
+ with timeout_util.Timeout(1):
+ self.assertRaises(timeout_util.TimeoutError, time.sleep, 10)
+
+ # Should not raise a timeout exception as 20 > 2.
+ time.sleep(1)
+
+ def testTimeoutNested(self):
+ """Tests that we still re-raise an alarm if both are reached."""
+ with timeout_util.Timeout(1):
+ try:
+ with timeout_util.Timeout(2):
+ self.assertRaises(timeout_util.TimeoutError, time.sleep, 1)
+
+ # Craziness to catch nested timeouts.
+ except timeout_util.TimeoutError:
+ pass
+ else:
+ self.assertTrue(False, 'Should have thrown an exception')
+
+
+class TestTreeStatus(cros_test_lib.MoxTestCase):
+ """Tests TreeStatus method in cros_build_lib."""
+
+ status_url = 'https://chromiumos-status.appspot.com/current?format=json'
+
+ def setUp(self):
+ pass
+
+ def _TreeStatusFile(self, message, general_state):
+ """Returns a file-like object with the status message writtin in it."""
+ my_response = self.mox.CreateMockAnything()
+ my_response.json = '{"message": "%s", "general_state": "%s"}' % (
+ message, general_state)
+ return my_response
+
+ def _SetupMockTreeStatusResponses(self, status_url,
+ final_tree_status='Tree is open.',
+ final_general_state=constants.TREE_OPEN,
+ rejected_tree_status='Tree is closed.',
+ rejected_general_state=
+ constants.TREE_CLOSED,
+ rejected_status_count=0,
+ retries_500=0,
+ output_final_status=True):
+ """Mocks out urllib.urlopen commands to simulate a given tree status.
+
+ Args:
+
+ status_url: The status url that status will be fetched from.
+ final_tree_status: The final value of tree status that will be returned
+ by urlopen.
+ final_general_state: The final value of 'general_state' that will be
+ returned by urlopen.
+ rejected_tree_status: An intermediate value of tree status that will be
+ returned by urlopen and retried upon.
+ rejected_general_state: An intermediate value of 'general_state' that
+ will be returned by urlopen and retried upon.
+ rejected_status_count: The number of times urlopen will return the
+ rejected state.
+ retries_500: The number of times urlopen will fail with a 500 code.
+ output_final_status: If True, the status given by final_tree_status and
+ final_general_state will be the last status returned by urlopen. If
+ False, final_tree_status will never be returned, and instead an
+ unlimited number of times rejected_response will be returned.
+ """
+
+ final_response = self._TreeStatusFile(final_tree_status,
+ final_general_state)
+ rejected_response = self._TreeStatusFile(rejected_tree_status,
+ rejected_general_state)
+ error_500_response = self.mox.CreateMockAnything()
+ self.mox.StubOutWithMock(urllib, 'urlopen')
+
+ for _ in range(retries_500):
+ urllib.urlopen(status_url).AndReturn(error_500_response)
+ error_500_response.getcode().AndReturn(500)
+
+ if output_final_status:
+ for _ in range(rejected_status_count):
+ urllib.urlopen(status_url).AndReturn(rejected_response)
+ rejected_response.getcode().AndReturn(200)
+ rejected_response.read().AndReturn(rejected_response.json)
+
+ urllib.urlopen(status_url).AndReturn(final_response)
+ final_response.getcode().AndReturn(200)
+ final_response.read().AndReturn(final_response.json)
+ else:
+ urllib.urlopen(status_url).MultipleTimes().AndReturn(rejected_response)
+ rejected_response.getcode().MultipleTimes().AndReturn(200)
+ rejected_response.read().MultipleTimes().AndReturn(
+ rejected_response.json)
+
+ self.mox.ReplayAll()
+
+ def testTreeIsOpen(self):
+ """Tests that we return True is the tree is open."""
+ self._SetupMockTreeStatusResponses(self.status_url,
+ rejected_status_count=5,
+ retries_500=5)
+ self.assertTrue(timeout_util.IsTreeOpen(self.status_url,
+ period=0))
+
+ def testTreeIsClosed(self):
+ """Tests that we return false is the tree is closed."""
+ self._SetupMockTreeStatusResponses(self.status_url,
+ output_final_status=False)
+ self.assertFalse(timeout_util.IsTreeOpen(self.status_url,
+ period=0.1))
+
+ def testTreeIsThrottled(self):
+ """Tests that we return True if the tree is throttled."""
+ self._SetupMockTreeStatusResponses(self.status_url,
+ 'Tree is throttled (flaky bug on flaky builder)',
+ constants.TREE_THROTTLED)
+ self.assertTrue(timeout_util.IsTreeOpen(self.status_url,
+ throttled_ok=True))
+
+ def testTreeIsThrottledNotOk(self):
+ """Tests that we respect throttled_ok"""
+ self._SetupMockTreeStatusResponses(self.status_url,
+ rejected_tree_status='Tree is throttled (flaky bug on flaky builder)',
+ rejected_general_state=constants.TREE_THROTTLED,
+ output_final_status=False)
+ self.assertFalse(timeout_util.IsTreeOpen(self.status_url,
+ period=0.1))
+
+ def testWaitForStatusOpen(self):
+ """Tests that we can wait for a tree open response."""
+ self._SetupMockTreeStatusResponses(self.status_url)
+ self.assertEqual(timeout_util.WaitForTreeStatus(self.status_url),
+ constants.TREE_OPEN)
+
+
+ def testWaitForStatusThrottled(self):
+ """Tests that we can wait for a tree open response."""
+ self._SetupMockTreeStatusResponses(self.status_url,
+ final_general_state=constants.TREE_THROTTLED)
+ self.assertEqual(timeout_util.WaitForTreeStatus(self.status_url,
+ throttled_ok=True),
+ constants.TREE_THROTTLED)
+
+ def testWaitForStatusFailure(self):
+ """Tests that we can wait for a tree open response."""
+ self._SetupMockTreeStatusResponses(self.status_url,
+ output_final_status=False)
+ self.assertRaises(timeout_util.TimeoutError,
+ timeout_util.WaitForTreeStatus, self.status_url,
+ period=0.1)
+
+
+if __name__ == '__main__':
+ cros_test_lib.main()
diff --git a/scripts/cbuildbot.py b/scripts/cbuildbot.py
index a3e60be..c08ce38 100644
--- a/scripts/cbuildbot.py
+++ b/scripts/cbuildbot.py
@@ -43,6 +43,7 @@
from chromite.lib import patch as cros_patch
from chromite.lib import parallel
from chromite.lib import sudo
+from chromite.lib import timeout_util
_DEFAULT_LOG_DIR = 'cbuildbot_logs'
@@ -1534,7 +1535,7 @@
stack.Add(critical_section.ForkWatchdog)
if options.timeout > 0:
- stack.Add(cros_build_lib.FatalTimeout, options.timeout)
+ stack.Add(timeout_util.FatalTimeout, options.timeout)
if not options.buildbot:
build_config = cbuildbot_config.OverrideConfigForTrybot(
diff --git a/scripts/cros_best_revision.py b/scripts/cros_best_revision.py
index 2dc2b67..af60d71 100644
--- a/scripts/cros_best_revision.py
+++ b/scripts/cros_best_revision.py
@@ -23,6 +23,7 @@
from chromite.lib import gs
from chromite.lib import osutils
from chromite.lib import parallel
+from chromite.lib import timeout_util
class LKGMNotFound(Exception):
@@ -136,7 +137,7 @@
commit_cmd = ['svn', 'commit', '--message',
self. _COMMIT_MSG % dict(version=self._lkgm)]
- if not cros_build_lib.IsTreeOpen(gclient.STATUS_URL,
+ if not timeout_util.IsTreeOpen(gclient.STATUS_URL,
self._SLEEP_TIMEOUT, timeout=self._TREE_TIMEOUT):
raise LKGMNotCommitted('Chromium Tree is closed')
diff --git a/scripts/cros_best_revision_unittest.py b/scripts/cros_best_revision_unittest.py
index 13758ee..9a45617 100755
--- a/scripts/cros_best_revision_unittest.py
+++ b/scripts/cros_best_revision_unittest.py
@@ -15,13 +15,13 @@
from chromite.buildbot import cbuildbot_config
from chromite.buildbot import constants
from chromite.buildbot import manifest_version
-from chromite.lib import cros_build_lib
from chromite.lib import cros_build_lib_unittest
from chromite.lib import cros_test_lib
from chromite.lib import gclient
from chromite.lib import gs_unittest
from chromite.lib import osutils
from chromite.lib import partial_mock
+from chromite.lib import timeout_util
from chromite.scripts import cros_best_revision
@@ -116,7 +116,7 @@
def testCommitNewLKGM(self):
"""Tests that we can commit a new LKGM file."""
self.committer._lkgm = '4.0.0'
- self.PatchObject(cros_build_lib, 'IsTreeOpen', return_value=True)
+ self.PatchObject(timeout_util, 'IsTreeOpen', return_value=True)
self.committer.CommitNewLKGM()
# Check the file was actually written out correctly.
diff --git a/scripts/deploy_chrome.py b/scripts/deploy_chrome.py
index 232d63a..9101d70 100644
--- a/scripts/deploy_chrome.py
+++ b/scripts/deploy_chrome.py
@@ -40,6 +40,7 @@
from chromite.lib import parallel
from chromite.lib import remote_access as remote
from chromite.lib import stats
+from chromite.lib import timeout_util
from chromite.scripts import lddtree
@@ -164,7 +165,7 @@
# Developers sometimes run session_manager manually, in which case we'll
# need to help shut the chrome processes down.
try:
- with cros_build_lib.Timeout(KILL_PROC_MAX_WAIT):
+ with timeout_util.Timeout(KILL_PROC_MAX_WAIT):
while self._ChromeFileInUse():
logging.warning('The chrome binary on the device is in use.')
logging.warning('Killing chrome and session_manager processes...\n')
@@ -174,7 +175,7 @@
# Wait for processes to actually terminate
time.sleep(POST_KILL_WAIT)
logging.info('Rechecking the chrome binary...')
- except cros_build_lib.TimeoutError:
+ except timeout_util.TimeoutError:
msg = ('Could not kill processes after %s seconds. Please exit any '
'running chrome processes and try again.' % KILL_PROC_MAX_WAIT)
raise DeployFailure(msg)