def test_transitive_params(self): # Test that C can be provided and implicitly converted into a B with transitive_b_c() to satisfy # the selectors of consumes_a_and_b(). a, c = A(), C() result_str, = self.scheduler.product_request(str, [Params(a, c)]) self.assertEquals(remove_locations_from_traceback(result_str), remove_locations_from_traceback(consumes_a_and_b(a, transitive_b_c(c)))) # Test that an inner Get in transitive_coroutine_rule() is able to resolve B from C due to the # existence of transitive_b_c(). result_d, = self.scheduler.product_request(D, [Params(c)]) # We don't need the inner B objects to be the same, and we know the arguments are type-checked, # we're just testing transitively resolving products in this file. self.assertTrue(isinstance(result_d, D))
def test_transitive_params(self): # Test that C can be provided and implicitly converted into a B with transitive_b_c() to satisfy # the selectors of consumes_a_and_b(). a, c = A(), C() result_str, = self.scheduler.product_request(str, [Params(a, c)]) self.assertEquals(remove_locations_from_traceback(result_str), remove_locations_from_traceback(consumes_a_and_b(a, transitive_b_c(c)))) # Test that an inner Get in transitive_coroutine_rule() is able to resolve B from C due to the # existence of transitive_b_c(). result_d, = self.scheduler.product_request(D, [Params(c)]) # We don't need the inner B objects to be the same, and we know the arguments are type-checked, # we're just testing transitively resolving products in this file. self.assertTrue(isinstance(result_d, D))
def test_trace_includes_rule_exception_traceback(self): rules = [ RootRule(B), nested_raise, ] scheduler = create_scheduler(rules) request = scheduler._native.new_execution_request() subject = B() scheduler.add_root_selection(request, subject, A) session = scheduler.new_session() scheduler._run_and_return_roots(session._session, request) trace = '\n'.join(scheduler.graph_trace(request)) # NB removing location info to make trace repeatable trace = remove_locations_from_traceback(trace) assert_equal_with_printing( self, dedent(''' Computing Select(<pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, A) Computing Task(nested_raise(), <pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, A, true) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B''').lstrip() + '\n\n', # Traces include two empty lines after. trace)
def test_include_trace_error_raises_error_with_trace(self): rules = [ RootRule(B), TaskRule(A, [Select(B)], nested_raise) ] scheduler = self.scheduler(rules, include_trace_on_error=True) with self.assertRaises(Exception) as cm: list(scheduler.product_request(A, subjects=[(B())])) self.assert_equal_with_printing(dedent(''' Received unexpected Throw state(s): Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(<function nested_raise at 0xEEEEEEEEE>, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in extern_invoke_runnable val = runnable(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B ''').lstrip()+'\n', remove_locations_from_traceback(str(cm.exception)))
def test_trace_does_not_include_cancellations(self): # Tests that when the computation of `Select(C)` fails, the cancellation of `Select(D)` # is not rendered as a failure. rules = [ RootRule(B), TaskRule(D, [Select(B)], D), TaskRule(C, [Select(B)], nested_raise), TaskRule(A, [Select(C), Select(D)], A), ] scheduler = self.scheduler(rules, include_trace_on_error=True) with self.assertRaises(Exception) as cm: list(scheduler.product_request(A, subjects=[(B())])) self.assert_equal_with_printing(dedent(''' Received unexpected Throw state(s): Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(<class 'pants_test.engine.test_engine.A'>, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(<function nested_raise at 0xEEEEEEEEE>, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =C) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B ''').lstrip()+'\n', remove_locations_from_traceback(str(cm.exception)))
def test_include_trace_error_raises_error_with_trace(self): rules = [ RootRule(B), nested_raise, ] scheduler = self.scheduler(rules, include_trace_on_error=True) with self.assertRaises(ExecutionError) as cm: list(scheduler.product_request(A, subjects=[(B())])) self.assert_equal_with_printing(dedent(''' 1 Exception encountered: Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, A) Computing Task(nested_raise(), <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, A, true) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B ''').lstrip()+'\n', remove_locations_from_traceback(str(cm.exception)))
def test_trace_includes_rule_exception_traceback(self): rules = [ RootRule(B), TaskRule(A, [Select(B)], nested_raise) ] scheduler = create_scheduler(rules) request = scheduler._native.new_execution_request() subject = B() scheduler.add_root_selection(request, subject, A) session = scheduler.new_session() scheduler._run_and_return_roots(session._session, request) trace = '\n'.join(scheduler.graph_trace(request)) # NB removing location info to make trace repeatable trace = remove_locations_from_traceback(trace) assert_equal_with_printing(self, dedent(''' Computing Select(<pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, =A) Computing Task(nested_raise, <pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, =A) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B''').lstrip() + '\n\n', # Traces include two empty lines after. trace)
def test_trace_includes_rule_exception_traceback(self): rules = [TaskRule(A, [Select(B)], nested_raise)] scheduler = create_native_scheduler({B}, rules) subject = B() scheduler.add_root_selection(subject, Select(A)) scheduler.run_and_return_stat() trace = '\n'.join(scheduler.graph_trace()) # NB removing location info to make trace repeatable trace = remove_locations_from_traceback(trace) assert_equal_with_printing( self, dedent(''' Computing Select(<pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, =A) Computing Task(<function nested_raise at 0xEEEEEEEEE>, <pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, =A) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in extern_invoke_runnable val = runnable(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B''').lstrip() + '\n\n', # Traces include two empty lines after. trace)
def test_trace_includes_rule_exception_traceback(self): rules = [ TaskRule(A, [Select(B)], nested_raise) ] scheduler = create_native_scheduler({B}, rules) subject = B() scheduler.add_root_selection(subject, Select(A)) scheduler.run_and_return_stat() trace = '\n'.join(scheduler.graph_trace()) # NB removing location info to make trace repeatable trace = remove_locations_from_traceback(trace) assert_equal_with_printing(self, dedent(''' Computing Select(<pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, =A) Computing Task(<function nested_raise at 0xEEEEEEEEE>, <pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, =A) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in extern_invoke_runnable val = runnable(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B''').lstrip() + '\n\n', # Traces include two empty lines after. trace)
def test_trace_multi(self): # Tests that when multiple distinct failures occur, they are each rendered. @rule(D, [B]) def d_from_b_nested_raise(b): fn_raises(b) @rule(C, [B]) def c_from_b_nested_raise(b): fn_raises(b) @rule(A, [C, D]) def a_from_c_and_d(c, d): return A() rules = [ RootRule(B), d_from_b_nested_raise, c_from_b_nested_raise, a_from_c_and_d, ] scheduler = self.scheduler(rules, include_trace_on_error=True) with self.assertRaises(ExecutionError) as cm: list(scheduler.product_request(A, subjects=[(B())])) self.assert_equal_with_printing(dedent(''' 1 Exception encountered: Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, A) Computing Task(a_from_c_and_d(), <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, A, true) Computing Task(d_from_b_nested_raise(), <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =D, true) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in d_from_b_nested_raise fn_raises(b) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, A) Computing Task(a_from_c_and_d(), <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, A, true) Computing Task(c_from_b_nested_raise(), <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =C, true) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in c_from_b_nested_raise fn_raises(b) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B ''').lstrip()+'\n', remove_locations_from_traceback(str(cm.exception)))
def test_trace_multi(self): # Tests that when multiple distinct failures occur, they are each rendered. rules = [ RootRule(B), TaskRule(D, [Select(B)], nested_raise), TaskRule(C, [Select(B)], nested_raise), TaskRule(A, [Select(C), Select(D)], A), ] scheduler = self.scheduler(rules, include_trace_on_error=True) with self.assertRaises(ExecutionError) as cm: list(scheduler.product_request(A, subjects=[(B())])) self.assert_equal_with_printing(dedent(''' 1 Exception encountered: Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(A, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(nested_raise, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =D) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(A, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(nested_raise, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =C) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B ''').lstrip()+'\n', remove_locations_from_traceback(str(cm.exception)))
def test_trace_includes_rule_exception_traceback(self): # Execute a request that will trigger the nested raise, and then directly inspect its trace. request = self.scheduler.execution_request([A], [B()]) self.scheduler.execute(request) trace = remove_locations_from_traceback('\n'.join(self.scheduler.trace(request))) assert_equal_with_printing(self, dedent(''' Computing Select(<pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, A) Computing Task(nested_raise(), <pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, A, true) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B''').lstrip() + '\n\n', # Traces include two empty lines after. trace)
def test_trace_includes_rule_exception_traceback(self): # Execute a request that will trigger the nested raise, and then directly inspect its trace. request = self.scheduler.execution_request([A], [B()]) self.scheduler.execute(request) trace = remove_locations_from_traceback('\n'.join(self.scheduler.trace(request))) assert_equal_with_printing(self, dedent(''' Computing Select(<pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, A) Computing Task(nested_raise(), <pants_test.engine.test_scheduler.B object at 0xEEEEEEEEE>, A, true) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in call val = func(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B''').lstrip() + '\n\n', # Traces include two empty lines after. trace)
def test_include_trace_error_raises_error_with_trace(self): rules = [RootRule(B), TaskRule(A, [Select(B)], nested_raise)] scheduler = self.scheduler(rules, include_trace_on_error=True) with self.assertRaises(Exception) as cm: list(scheduler.product_request(A, subjects=[(B())])) self.assert_equal_with_printing( dedent(''' Received unexpected Throw state(s): Computing Select(<pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Computing Task(<function nested_raise at 0xEEEEEEEEE>, <pants_test.engine.test_engine.B object at 0xEEEEEEEEE>, =A) Throw(An exception for B) Traceback (most recent call last): File LOCATION-INFO, in extern_invoke_runnable val = runnable(*args) File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception('An exception for {}'.format(type(x).__name__)) Exception: An exception for B ''').lstrip() + '\n', remove_locations_from_traceback(str(cm.exception)))
def test_unhashable_failure(self): """Test that unhashable Get(...) params result in a structured error.""" def assert_has_cffi_extern_traceback_header(exc_str): self.assertTrue(exc_str.startswith(dedent("""\ 1 Exception raised in CFFI extern methods: Traceback (most recent call last): """)), "exc_str was: {}".format(exc_str)) def assert_has_end_of_cffi_extern_error_traceback(exc_str): self.assertIn(dedent("""\ Traceback (most recent call last): File LOCATION-INFO, in extern_identify return c.identify(obj) File LOCATION-INFO, in identify hash_ = hash(obj) File LOCATION-INFO, in __hash__ .format(self, type(self).__name__, field_name, e)) TypeError: For datatype object CollectionType(items=[1, 2, 3]) (type 'CollectionType'): in field 'items': unhashable type: 'list' """), exc_str, "exc_str was: {}".format(exc_str)) resulting_engine_error = "Exception: No installed @rules can satisfy Select(C) for input Params(Any).\n\n\n" # Test that the error contains the full traceback from within the CFFI context as well # (mentioning which specific extern method ended up raising the exception). with self.assertRaises(ExecutionError) as cm: self.scheduler.product_request(C, [Params(CollectionType([1, 2, 3]))]) exc_str = remove_locations_from_traceback(str(cm.exception)) # TODO: convert these manual self.assertTrue() conditionals to a self.assertStartsWith() method # in TestBase! assert_has_cffi_extern_traceback_header(exc_str) assert_has_end_of_cffi_extern_error_traceback(exc_str) self.assertIn(dedent("""\ The engine execution request raised this error, which is probably due to the errors in the CFFI extern methods listed above, as CFFI externs return None upon error: """), exc_str) self.assertTrue(exc_str.endswith(resulting_engine_error), "exc_str was: {}".format(exc_str)) PATCH_OPTS = dict(autospec=True, spec_set=True) def create_cffi_exception(): try: raise Exception('test cffi exception') except: # noqa: T803 return Native.CFFIExternMethodRuntimeErrorInfo(*sys.exc_info()[0:3]) # Test that CFFI extern method errors result in an ExecutionError, even if .execution_request() # succeeds. with self.assertRaises(ExecutionError) as cm: with mock.patch.object(SchedulerSession, 'execution_request', **PATCH_OPTS) as mock_exe_request: with mock.patch.object(Native, 'cffi_extern_method_runtime_exceptions', **PATCH_OPTS) as mock_cffi_exceptions: mock_exe_request.return_value = None mock_cffi_exceptions.return_value = [create_cffi_exception()] self.scheduler.product_request(C, [Params(CollectionType([1, 2, 3]))]) exc_str = remove_locations_from_traceback(str(cm.exception)) assert_has_cffi_extern_traceback_header(exc_str) self.assertIn("Exception: test cffi exception", exc_str) self.assertNotIn(resulting_engine_error, exc_str) # Test that an error in the .execution_request() method is propagated directly, even if there # are no CFFI extern methods. class TestError(Exception): pass with self.assertRaisesWithMessage(TestError, 'non-CFFI error'): with mock.patch.object(SchedulerSession, 'execution_request', **PATCH_OPTS) as mock_exe_request: mock_exe_request.side_effect = TestError('non-CFFI error') self.scheduler.product_request(C, [Params(CollectionType([1, 2, 3]))])
def assert_execution_error(test_case, expected_msg): with test_case.assertRaises(ExecutionError) as cm: yield test_case.assertIn(expected_msg, remove_locations_from_traceback(str(cm.exception)))
def assert_execution_error(test_case, expected_msg): with test_case.assertRaises(ExecutionError) as cm: yield test_case.assertIn(expected_msg, remove_locations_from_traceback(str(cm.exception)))
def _assert_execution_error(self, expected_msg): with self.assertRaises(ExecutionError) as cm: yield self.assertIn(expected_msg, remove_locations_from_traceback(str(cm.exception)))
def test_unhashable_failure(self): """Test that unhashable Get(...) params result in a structured error.""" def assert_has_cffi_extern_traceback_header(exc_str): self.assertTrue( exc_str.startswith( dedent("""\ 1 Exception raised in CFFI extern methods: Traceback (most recent call last): """)), "exc_str was: {}".format(exc_str)) def assert_has_end_of_cffi_extern_error_traceback(exc_str): self.assertIn( dedent("""\ Traceback (most recent call last): File LOCATION-INFO, in extern_identify return c.identify(obj) File LOCATION-INFO, in identify hash_ = hash(obj) File LOCATION-INFO, in __hash__ .format(self, type(self).__name__, field_name, e)) TypeError: For datatype object CollectionType(items=[1, 2, 3]) (type 'CollectionType'): in field 'items': unhashable type: 'list' """), exc_str, "exc_str was: {}".format(exc_str)) resulting_engine_error = "Exception: No installed @rules can compute C for input Params(Any).\n\n\n" # Test that the error contains the full traceback from within the CFFI context as well # (mentioning which specific extern method ended up raising the exception). with self.assertRaises(ExecutionError) as cm: self.scheduler.product_request(C, [Params(CollectionType([1, 2, 3]))]) exc_str = remove_locations_from_traceback(str(cm.exception)) # TODO: convert these manual self.assertTrue() conditionals to a self.assertStartsWith() method # in TestBase! assert_has_cffi_extern_traceback_header(exc_str) assert_has_end_of_cffi_extern_error_traceback(exc_str) self.assertIn( dedent("""\ The engine execution request raised this error, which is probably due to the errors in the CFFI extern methods listed above, as CFFI externs return None upon error: """), exc_str) self.assertTrue(exc_str.endswith(resulting_engine_error), "exc_str was: {}".format(exc_str)) PATCH_OPTS = dict(autospec=True, spec_set=True) def create_cffi_exception(): try: raise Exception('test cffi exception') except: # noqa: T803 return Native.CFFIExternMethodRuntimeErrorInfo( *sys.exc_info()[0:3]) # Test that CFFI extern method errors result in an ExecutionError, even if .execution_request() # succeeds. with self.assertRaises(ExecutionError) as cm: with unittest.mock.patch.object(SchedulerSession, 'execution_request', **PATCH_OPTS) as mock_exe_request: with unittest.mock.patch.object( Native, 'cffi_extern_method_runtime_exceptions', **PATCH_OPTS) as mock_cffi_exceptions: mock_exe_request.return_value = None mock_cffi_exceptions.return_value = [ create_cffi_exception() ] self.scheduler.product_request( C, [Params(CollectionType([1, 2, 3]))]) exc_str = remove_locations_from_traceback(str(cm.exception)) assert_has_cffi_extern_traceback_header(exc_str) self.assertIn("Exception: test cffi exception", exc_str) self.assertNotIn(resulting_engine_error, exc_str) # Test that an error in the .execution_request() method is propagated directly, even if there # are no CFFI extern methods. class TestError(Exception): pass with self.assertRaisesWithMessage(TestError, 'non-CFFI error'): with unittest.mock.patch.object(SchedulerSession, 'execution_request', **PATCH_OPTS) as mock_exe_request: mock_exe_request.side_effect = TestError('non-CFFI error') self.scheduler.product_request( C, [Params(CollectionType([1, 2, 3]))])