diff --git a/src/python/pants/bin/local_pants_runner.py b/src/python/pants/bin/local_pants_runner.py index 27116050e73..fbc88112121 100644 --- a/src/python/pants/bin/local_pants_runner.py +++ b/src/python/pants/bin/local_pants_runner.py @@ -92,12 +92,14 @@ def _run(self): self._daemon_build_graph, self._exiter).setup() - result = goal_runner.run() + goal_runner_result = goal_runner.run() if repro: # TODO: Have Repro capture the 'after' state (as a diff) as well? repro.log_location_of_repro_file() finally: - run_tracker.end() + run_tracker_result = run_tracker.end() - self._exiter.exit(result) + # Take the exit code with higher abs value in case of negative values. + final_exit_code = goal_runner_result if abs(goal_runner_result) > abs(run_tracker_result) else run_tracker_result + self._exiter.exit(final_exit_code) diff --git a/src/python/pants/goal/run_tracker.py b/src/python/pants/goal/run_tracker.py index 42dcd5f9acf..c434895b064 100644 --- a/src/python/pants/goal/run_tracker.py +++ b/src/python/pants/goal/run_tracker.py @@ -86,8 +86,8 @@ def __init__(self, *args, **kwargs): # run_id is safe for use in paths. millis = int((run_timestamp * 1000) % 1000) run_id = 'pants_run_{}_{}_{}'.format( - time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime(run_timestamp)), millis, - uuid.uuid4().hex) + time.strftime('%Y_%m_%d_%H_%M_%S', time.localtime(run_timestamp)), millis, + uuid.uuid4().hex) info_dir = os.path.join(self.get_options().pants_workdir, self.options_scope) self.run_info_dir = os.path.join(info_dir, run_id) @@ -317,6 +317,8 @@ def end(self): """This pants run is over, so stop tracking it. Note: If end() has been called once, subsequent calls are no-ops. + + :return: 0 for success, 1 for failure. """ if self._background_worker_pool: if self._aborted: @@ -349,6 +351,8 @@ def end(self): self.report.close() self.store_stats() + return 1 if outcome in [WorkUnit.FAILURE, WorkUnit.ABORTED] else 0 + def end_workunit(self, workunit): self.report.end_workunit(workunit) path, duration, self_time, is_tool = workunit.end() diff --git a/tests/python/pants_test/goal/data/__init__.py b/tests/python/pants_test/goal/data/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/python/pants_test/goal/data/register.py b/tests/python/pants_test/goal/data/register.py new file mode 100644 index 00000000000..0fa98a9a212 --- /dev/null +++ b/tests/python/pants_test/goal/data/register.py @@ -0,0 +1,28 @@ +# coding=utf-8 +# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +from __future__ import (absolute_import, division, generators, nested_scopes, print_function, + unicode_literals, with_statement) + +from pants.backend.jvm.tasks.nailgun_task import NailgunTask +from pants.base.workunit import WorkUnit +from pants.goal.task_registrar import TaskRegistrar as task +from pants.task.task import Task + + +def register_goals(): + task(name='run-dummy-workunit', action=TestWorkUnitTask).install() + + +class TestWorkUnitTask(NailgunTask): + @classmethod + def register_options(cls, register): + register('--success', default=False, type=bool) + + def execute(self): + result = WorkUnit.SUCCESS if self.get_options().success else WorkUnit.FAILURE + + # This creates workunit and marks it as failure. + with self.context.new_workunit('dummy') as workunit: + workunit.set_outcome(result) diff --git a/tests/python/pants_test/goal/test_run_tracker_integration.py b/tests/python/pants_test/goal/test_run_tracker_integration.py index 712be9e031b..10712c92ee8 100644 --- a/tests/python/pants_test/goal/test_run_tracker_integration.py +++ b/tests/python/pants_test/goal/test_run_tracker_integration.py @@ -6,6 +6,7 @@ unicode_literals, with_statement) import json +import os from pants.util.contextutil import temporary_file_path from pants_test.pants_run_integration_test import PantsRunIntegrationTest @@ -27,3 +28,24 @@ def test_stats_local_json_file(self): self.assertIn('run_info', stats_json) self.assertIn('self_timings', stats_json) self.assertIn('cumulative_timings', stats_json) + + def test_workunit_failure(self): + pants_run = self.run_pants([ + '--pythonpath={}'.format(os.path.join(os.getcwd(), 'tests', 'python')), + '--backend-packages={}'.format('pants_test.goal.data'), + 'run-dummy-workunit', + '--no-success' + ]) + # Make sure the task actually happens and of no exception. + self.assertIn('[run-dummy-workunit]', pants_run.stdout_data) + self.assertNotIn('Exception', pants_run.stderr_data) + self.assert_failure(pants_run) + + def test_workunit_success(self): + pants_run = self.run_pants([ + '--pythonpath={}'.format(os.path.join(os.getcwd(), 'tests', 'python')), + '--backend-packages={}'.format('pants_test.goal.data'), + 'run-dummy-workunit', + '--success' + ]) + self.assert_success(pants_run)