diff --git a/src/python/pants/engine/scheduler.py b/src/python/pants/engine/scheduler.py index 34ae1a10fa76..4a492a8abdbf 100644 --- a/src/python/pants/engine/scheduler.py +++ b/src/python/pants/engine/scheduler.py @@ -68,7 +68,9 @@ def failure(cls, error): class ExecutionError(Exception): - pass + def __init__(self, message, wrapped_exceptions): + super(ExecutionError, self).__init__(message) + self.wrapped_exceptions = wrapped_exceptions or () class Scheduler(object): @@ -516,17 +518,23 @@ def products_request(self, products, subjects): # TODO: See https://github.com/pantsbuild/pants/issues/3912 throw_root_states = tuple(state for root, state in result.root_products if type(state) is Throw) if throw_root_states: + unique_exceptions = tuple(set(t.exc for t in throw_root_states)) + if self._scheduler.include_trace_on_error: cumulative_trace = '\n'.join(self.trace(request)) - raise ExecutionError('Received unexpected Throw state(s):\n{}'.format(cumulative_trace)) + raise ExecutionError( + 'Received unexpected Throw state(s):\n{}'.format(cumulative_trace), + unique_exceptions, + ) - unique_exceptions = set(t.exc for t in throw_root_states) if len(unique_exceptions) == 1: raise throw_root_states[0].exc else: - raise ExecutionError('Multiple exceptions encountered:\n {}' - .format('\n '.join('{}: {}'.format(type(t).__name__, str(t)) - for t in unique_exceptions))) + raise ExecutionError( + 'Multiple exceptions encountered:\n {}'.format( + '\n '.join('{}: {}'.format(type(t).__name__, str(t)) for t in unique_exceptions)), + unique_exceptions + ) # Everything is a Return: we rely on the fact that roots are ordered to preserve subject # order in output lists. diff --git a/tests/python/pants_test/engine/test_isolated_process.py b/tests/python/pants_test/engine/test_isolated_process.py index 9d0f5c4e06bb..9acb5abbc470 100644 --- a/tests/python/pants_test/engine/test_isolated_process.py +++ b/tests/python/pants_test/engine/test_isolated_process.py @@ -12,6 +12,7 @@ PathGlobs, Snapshot) from pants.engine.isolated_process import ExecuteProcessRequest, ExecuteProcessResult from pants.engine.rules import RootRule, rule +from pants.engine.scheduler import ExecutionError from pants.engine.selectors import Get, Select from pants.util.objects import TypeCheckError, datatype from pants_test.test_base import TestBase @@ -300,12 +301,12 @@ def test_integration_concat_with_snapshots_stdout(self): PathGlobs(include=['f*']), ) - concatted = self.scheduler_execute_expecting_one_result(Concatted, cat_exe_req) + concatted = self.scheduler.product_request(Concatted, [cat_exe_req])[0] self.assertEqual(Concatted(str('one\ntwo\n')), concatted) def test_javac_version_example(self): request = JavacVersionExecutionRequest(BinaryLocation('/usr/bin/javac')) - result = self.scheduler_execute_expecting_one_result(JavacVersionOutput, request) + result = self.scheduler.product_request(JavacVersionOutput, [request])[0] self.assertIn('javac', result.value) def test_write_file(self): @@ -315,10 +316,10 @@ def test_write_file(self): ("roland",) ) - execute_process_result = self.scheduler_execute_expecting_one_result( + execute_process_result = self.scheduler.product_request( ExecuteProcessResult, - request, - ) + [request], + )[0] self.assertEquals( execute_process_result.output_directory_digest, @@ -328,10 +329,10 @@ def test_write_file(self): ) ) - files_content_result = self.scheduler_execute_expecting_one_result( + files_content_result = self.scheduler.product_request( FilesContent, - execute_process_result.output_directory_digest - ) + [execute_process_result.output_directory_digest], + )[0] self.assertEquals( files_content_result.dependencies, @@ -351,7 +352,7 @@ def test_exercise_python_side_of_timeout_implementation(self): description='sleepy-cat', ) - self.scheduler_execute_expecting_one_result(ExecuteProcessResult, request) + self.scheduler.product_request(ExecuteProcessResult, [request])[0] def test_javac_compilation_example_success(self): self.create_dir('simple') @@ -366,8 +367,8 @@ class Simple { JavacSources((u'simple/Simple.java',)), ) - result = self.scheduler_execute_expecting_one_result(JavacCompileResult, request) - files_content = self.scheduler_execute_expecting_one_result(FilesContent, result.directory_digest).dependencies + result = self.scheduler.product_request(JavacCompileResult, [request])[0] + files_content = self.scheduler.product_request(FilesContent, [result.directory_digest])[0].dependencies self.assertEquals( ("simple/Simple.class",), @@ -388,8 +389,9 @@ class Broken { JavacSources(('simple/Broken.java',)) ) - with self.assertRaises(ProcessExecutionFailure) as cm: - self.scheduler_execute_expecting_one_result(JavacCompileResult, request) - e = cm.exception + with self.assertRaises(ExecutionError) as cm: + self.scheduler.product_request(JavacCompileResult, [request])[0] + e = cm.exception.wrapped_exceptions[0] + self.assertIsInstance(e, ProcessExecutionFailure) self.assertEqual(1, e.exit_code) self.assertIn("NOT VALID JAVA", e.stderr) diff --git a/tests/python/pants_test/test_base.py b/tests/python/pants_test/test_base.py index 22a402450bb5..48a24b68c02a 100644 --- a/tests/python/pants_test/test_base.py +++ b/tests/python/pants_test/test_base.py @@ -23,7 +23,6 @@ from pants.build_graph.build_file_aliases import BuildFileAliases from pants.build_graph.build_file_parser import BuildFileParser from pants.build_graph.target import Target -from pants.engine.nodes import Throw from pants.init.engine_initializer import EngineInitializer from pants.init.util import clean_global_runtime_state from pants.option.options_bootstrapper import OptionsBootstrapper @@ -353,21 +352,6 @@ def scheduler(self): self._init_engine() return self._scheduler - def scheduler_execute_expecting_one_result(self, product, subject): - request = self.scheduler.execution_request([product], [subject]) - result = self.scheduler.execute(request) - - if result.error: - raise result.error - - states = [state for _, state in result.root_products] - self.assertEqual(len(states), 1) - - state = states[0] - if isinstance(state, Throw): - raise state.exc - return state.value - @property def address_mapper(self): if self._address_mapper is None: