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.request_single_product(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(). with self.assertDoesNotRaise(): _ = self.request_single_product(D, Params(c))
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_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( f""" 1 Exception encountered: Computing Select(<{__name__}.B object at 0xEEEEEEEEE>, A) Computing Task({fmt_rust_function(nested_raise)}(), <{__name__}.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(f"An exception for {{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: Traceback (most recent call last): File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception(f"An exception for {type(x).__name__}") Exception: An exception for B """ ).lstrip(), 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()]) _, throws = self.scheduler.execute(request) with self.assertRaises(ExecutionError) as cm: self.scheduler._raise_on_error([t for _, t in throws]) trace = remove_locations_from_traceback(str(cm.exception)) assert_equal_with_printing( self, dedent("""\ 1 Exception encountered: Engine traceback: in Nested raise Traceback (most recent call last): File LOCATION-INFO, in nested_raise fn_raises(x) File LOCATION-INFO, in fn_raises raise Exception(f"An exception for {type(x).__name__}") Exception: An exception for B """), 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( f"""\ Computing Select(B(), A) Computing Task({fmt_rust_function(nested_raise)}(), B(), 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(f"An exception for {{type(x).__name__}}") Exception: An exception for B""" ) + "\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 def d_from_b_nested_raise(b: B) -> D: # type: ignore[return] fn_raises(b) @rule def c_from_b_nested_raise(b: B) -> C: # type: ignore[return] fn_raises(b) @rule def a_from_c_and_d(c: C, d: D) -> A: 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(f""" 1 Exception encountered: Computing Select(<{__name__}..B object at 0xEEEEEEEEE>, A) Computing Task(a_from_c_and_d(), <{__name__}..B object at 0xEEEEEEEEE>, A, true) Computing Task(d_from_b_nested_raise(), <{__name__}..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(<{__name__}..B object at 0xEEEEEEEEE>, A) Computing Task(a_from_c_and_d(), <{__name__}..B object at 0xEEEEEEEEE>, A, true) Computing Task(c_from_b_nested_raise(), <{__name__}..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 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 test_unhashable_failure(self): """Test that unhashable Get(...) params result in a structured error.""" def assert_has_cffi_extern_traceback_header(exception: str) -> None: assert exception.startswith( dedent("""\ 1 Exception raised in CFFI extern methods: Traceback (most recent call last): """)) def assert_has_end_of_cffi_extern_error_traceback( exception: str) -> None: assert "TypeError: unhashable type: 'list'" in exception canonical_exception_text = dedent("""\ The above exception was the direct cause of the following exception: Traceback (most recent call last): File LOCATION-INFO, in extern_identify return c.identify(obj) File LOCATION-INFO, in identify raise TypeError(f"failed to hash object {obj}: {e}") from e TypeError: failed to hash object CollectionType(items=[1, 2, 3]): unhashable type: 'list' """) assert canonical_exception_text in exception resulting_engine_error = dedent("""\ Exception: Types that will be passed as Params at the root of a graph need to be registered via RootRule: 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.request_single_product(C, Params(CollectionType([1, 2, 3]))) exc_str = remove_locations_from_traceback(str(cm.exception)) 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), f"exc_str was: {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, "_peek_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.request_single_product( 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.request_single_product(C, Params(CollectionType([1, 2, 3])))
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): """)), f"exc_str was: {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 "<string>", line 2, in __hash__ TypeError: unhashable type: 'list' """), exc_str, f"exc_str was: {exc_str}") resulting_engine_error = dedent("""\ Exception: Types that will be passed as Params at the root of a graph need to be registered via RootRule: 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), f"exc_str was: {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, '_peek_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]))])