# 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.