blob: efdfe30bc3fa65cfdb2ec5f7753a8c5e78107d20 [file] [log] [blame]
# Copyright 2020 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Helper functions for common boilerplate tasks.
Top-level functions stored in __init__.py can be imported and used directly
(without needing a `RecipeApi` object) using `RECIPE_MODULES` imports. E.g.:
from RECIPE_MODULES.fuchsia.utils import pluralize
"""
import traceback
from recipe_engine import recipe_api
class UtilsApi(recipe_api.RecipeApi):
def retry(self, func, max_attempts, sleep=5.0, backoff_factor=1.5):
"""Retry the given function with exponential backoff.
Args:
func (callable): A function that performs the action that should
be retried on failure. If it raises a `StepFailure` it will
be retried. Any other exception will end the retry loop and
bubble up.
max_attempts (int): How many times to try before giving up.
sleep (int or float): The time to sleep after the first attempt.
backoff_factor (int or float): The factor by which the sleep time
will be multiplied after each attempt.
"""
for attempt in range(max_attempts):
try:
return func()
except self.m.step.StepFailure as ex:
# We shouldn't retry if the step was cancelled because the build
# is likely being shut down.
#
# As of 2021-12-22 there's no way to simulate step cancellation
# in tests, so disable coverage.
if ex.was_cancelled: # pragma: no cover
raise
if attempt == max_attempts - 1:
raise
self.m.time.sleep(sleep)
sleep *= backoff_factor
def traceback_format_exc(self):
"""Simplify expectation file representation of exceptions.
If in recipe unit testing, reduce the output of the exception to just
the first line, cutting off the stack trace. This means expectation
files don't change just because an unrelated line was added at another
point in a source file. This is a no-op in production.
Must be called from within an "except:" block.
"""
text = traceback.format_exc()
if self._test_data.enabled:
# Prune the traceback so that the checked-in test expectations
# don't contain source code line numbers, which often change.
lines = text.splitlines()
text = (
"%s\n... <traceback text omitted to simplify test expectations>\n%s"
% (lines[0], lines[-1])
)
return text