# HG changeset patch
# User wbell
# Date 1272116305 0
# Node ID ab91df14f670d5881ee285cac70f840a0201d7a3
# Parent d9f06a314db58a43a096c5b558e21da4407c5ac9
Merge of [832] from trunk.
diff --git a/bitten/master.py b/bitten/master.py
--- a/bitten/master.py
+++ b/bitten/master.py
@@ -353,7 +353,8 @@
for num, recipe_step in enumerate(recipe):
step = BuildStep.fetch(self.env, build.id, recipe_step.id)
if step.status == BuildStep.FAILURE:
- if recipe_step.onerror != 'ignore':
+ if recipe_step.onerror == 'fail' or \
+ recipe_step.onerror == 'continue':
build.status = Build.FAILURE
break
else:
diff --git a/bitten/recipe.py b/bitten/recipe.py
--- a/bitten/recipe.py
+++ b/bitten/recipe.py
@@ -202,7 +202,7 @@
their keyword arguments.
"""
- def __init__(self, elem):
+ def __init__(self, elem, onerror_default):
"""Create the step.
:param elem: the XML element representing the step
@@ -211,7 +211,8 @@
self._elem = elem
self.id = elem.attr['id']
self.description = elem.attr.get('description')
- self.onerror = elem.attr.get('onerror', 'fail')
+ self.onerror = elem.attr.get('onerror', onerror_default)
+ assert self.onerror in ('fail', 'ignore', 'continue')
def __repr__(self):
return '<%s %r>' % (type(self).__name__, self.id)
@@ -272,11 +273,13 @@
if not name.startswith('xmlns')])
self.ctxt = Context(basedir, config, vars)
self._root = xml
+ self.onerror_default = vars.get('onerror', 'fail')
+ assert self.onerror_default in ('fail', 'ignore', 'continue')
def __iter__(self):
"""Iterate over the individual steps of the recipe."""
for child in self._root.children('step'):
- yield Step(child)
+ yield Step(child, self.onerror_default)
def validate(self):
"""Validate the recipe.
diff --git a/bitten/slave.py b/bitten/slave.py
--- a/bitten/slave.py
+++ b/bitten/slave.py
@@ -303,10 +303,14 @@
os.mkdir(basedir)
for step in recipe:
- log.info('Executing build step %r', step.id)
- if not self._execute_step(build_url, recipe, step):
- log.warning('Stopping build due to failure')
- break
+ try:
+ log.info('Executing build step %r, onerror = %s', step.id, step.onerror)
+ if not self._execute_step(build_url, recipe, step):
+ log.warning('Stopping build due to failure')
+ break
+ except Exception, e:
+ log.error('Exception raised processing step %s. Reraising %s', step.id, e)
+ raise
else:
log.info('Build completed')
if self.dry_run:
@@ -361,7 +365,6 @@
except KeyboardInterrupt:
log.warning('Build interrupted')
self._cancel_build(build_url)
-
return not failed or step.onerror != 'fail'
def _cancel_build(self, build_url, exit_code=EX_OK):
diff --git a/bitten/tests/master.py b/bitten/tests/master.py
--- a/bitten/tests/master.py
+++ b/bitten/tests/master.py
@@ -831,6 +831,56 @@
self.assertEqual('foo', steps[0].name)
self.assertEqual(BuildStep.FAILURE, steps[0].status)
+
+ def test_process_build_step_failure_continue(self):
+ recipe = """
+
+
+"""
+ BuildConfig(self.env, 'test', path='somepath', active=True,
+ recipe=recipe).insert()
+ build = Build(self.env, 'test', '123', 1, slave='hal', rev_time=42,
+ started=42, status=Build.IN_PROGRESS)
+ build.slave_info[Build.TOKEN] = '123';
+
+ build.insert()
+
+ inbody = StringIO("""
+""")
+ outheaders = {}
+ outbody = StringIO()
+ req = Mock(method='POST', base_path='',
+ path_info='/builds/%d/steps/' % build.id,
+ href=Href('/trac'), abs_href=Href('http://example.org/trac'),
+ remote_addr='127.0.0.1', args={},
+ perm=PermissionCache(self.env, 'hal'),
+ read=inbody.read,
+ send_response=lambda x: outheaders.setdefault('Status', x),
+ send_header=lambda x, y: outheaders.setdefault(x, y),
+ write=outbody.write,
+ incookie=Cookie('trac_auth=123'))
+ module = BuildMaster(self.env)
+ assert module.match_request(req)
+
+ self.assertRaises(RequestDone, module.process_request, req)
+
+ self.assertEqual(201, outheaders['Status'])
+ self.assertEqual('20', outheaders['Content-Length'])
+ self.assertEqual('text/plain', outheaders['Content-Type'])
+ self.assertEqual('Build step processed', outbody.getvalue())
+
+ build = Build.fetch(self.env, build.id)
+ self.assertEqual(Build.FAILURE, build.status)
+ assert build.stopped
+ assert build.stopped > build.started
+
+ steps = list(BuildStep.select(self.env, build.id))
+ self.assertEqual(1, len(steps))
+ self.assertEqual('foo', steps[0].name)
+ self.assertEqual(BuildStep.FAILURE, steps[0].status)
+
def test_process_build_step_invalid_xml(self):
recipe = """
diff --git a/bitten/tests_slave/recipe.py b/bitten/tests_slave/recipe.py
--- a/bitten/tests_slave/recipe.py
+++ b/bitten/tests_slave/recipe.py
@@ -175,6 +175,30 @@
recipe = Recipe(xml, basedir=self.basedir)
recipe.validate()
+ def test_onerror_defaults(self):
+ xml = xmlio.parse(''
+ ' '
+ '')
+ recipe = Recipe(xml, basedir=self.basedir)
+ steps = list(recipe)
+ self.assertEqual(1, len(steps))
+ self.assertEqual('foo', steps[0].id)
+ self.assertEqual('Bar', steps[0].description)
+ self.assertEqual('continue', steps[0].onerror)
+
+
+ def test_onerror_override(self):
+ xml = xmlio.parse(''
+ ' '
+ '')
+ recipe = Recipe(xml, basedir=self.basedir)
+ steps = list(recipe)
+ self.assertEqual(1, len(steps))
+ self.assertEqual('foo', steps[0].id)
+ self.assertEqual('Bar', steps[0].description)
+ self.assertEqual('continue', steps[0].onerror)
+
+
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(ContextTestCase, 'test'))
diff --git a/doc/recipes.txt b/doc/recipes.txt
--- a/doc/recipes.txt
+++ b/doc/recipes.txt
@@ -36,9 +36,19 @@
these elements are declared in XML namespaces, where the namespace URI defines
a collection of related commands.
-A ```` element can additionally have an ``onerror`` attribute with
-value of ``fail`` (terminate after step, default behaviour) or ``ignore``
-(fail, but run next step).
+
+The ```` element can optionally have an ``onerror`` attribute that
+dictates how a build should proceed after the failure of a step. Allowable
+values are:
+ ``fail``: failure of a step causes the build to terminate. (default)
+ ``continue``: builds continue after step failures. Failing steps
+ contribute to the overall build status.
+ ``ignore``: builds continue after step failures. Builds are marked
+ as successful even in the presence of failed steps with
+ onerror='ignore'
+
+```` elements can override the ```` ``onerror`` attribute with
+their own ``onerror`` attributes.
Commonly, the first step of any build recipe will perform the checkout from the
repository.