def test_unreachable_rule(self): """Test that when one rule "shadows" another, we get an error.""" @rule def d_singleton() -> D: return D() @rule def d_for_b(b: B) -> D: return D() rules = [ d_singleton, d_for_b, RootRule(B), ] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(f"""\ Rules with errors: 1 {fmt_rule(d_for_b)}: Was not reachable, either because no rules could produce the params or because it was shadowed by another @rule. """).strip(), str(cm.exception), )
def test_ruleset_with_failure_due_to_incompatible_subject_for_singleton( self): @rule def d_from_c(c: C) -> D: pass @rule def b_singleton() -> B: return B() rules = [ RootRule(A), d_from_c, b_singleton, ] with self.assertRaises(Exception) as cm: create_scheduler(rules) # TODO: This error message could note near matches like the singleton. self.assert_equal_with_printing( dedent(f"""\ Rules with errors: 1 {fmt_rule(d_from_c)}: No rule was available to compute C with parameter type A """).strip(), str(cm.exception), )
def test_ruleset_with_selector_only_provided_as_root_subject(self): @rule def a_from_b(b: B) -> A: pass rules = [RootRule(B), a_from_b] create_scheduler(rules)
def test_ruleset_with_superclass_of_selected_type_produced_fails(self): @rule def a_from_b(b: B) -> A: pass @rule def b_from_suba(suba: SubA) -> B: pass rules = [ RootRule(C), a_from_b, b_from_suba, ] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(f"""\ Rules with errors: 2 {fmt_rule(a_from_b)}: No rule was available to compute B with parameter type C {fmt_rule(b_from_suba)}: No rule was available to compute SubA with parameter type C """).strip(), str(cm.exception), )
def test_unreachable_rule(self): """Test that when one rule "shadows" another, we get an error.""" @rule def d_singleton() -> D: return D() @rule def d_for_b(b: B) -> D: return D() rules = [ d_singleton, d_for_b, RootRule(B), ] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(""" Rules with errors: 1 (D, [B], d_for_b()): Was not usable by any other @rule. """).strip(), str(cm.exception))
def test_not_fulfillable_duplicated_dependency(self): # If a rule depends on another rule+subject in two ways, and one of them is unfulfillable # Only the unfulfillable one should be in the errors. @rule def b_from_d(d: D) -> B: pass @rule async def d_from_a_and_suba(a: A, suba: SubA) -> D: _ = await Get(A, C, C()) # noqa: F841 @rule def a_from_c(c: C) -> A: pass rules = _suba_root_rules + [ b_from_d, d_from_a_and_suba, a_from_c, ] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(""" Rules with errors: 3 (A, [C], a_from_c()): Was not usable by any other @rule. (B, [D], b_from_d()): No rule was available to compute D with parameter type SubA (D, [A, SubA], [Get(A, C)], d_from_a_and_suba()): No rule was available to compute A with parameter type SubA """).strip(), str(cm.exception))
def test_ruleset_with_rule_with_two_missing_selects(self): @rule def a_from_b_and_c(b: B, c: C) -> A: pass rules = _suba_root_rules + [a_from_b_and_c] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(""" Rules with errors: 1 (A, [B, C], a_from_b_and_c()): No rule was available to compute B with parameter type SubA No rule was available to compute C with parameter type SubA """).strip(), str(cm.exception))
def test_ruleset_with_missing_product_type(self): @rule def a_from_b_noop(b: B) -> A: pass rules = _suba_root_rules + [a_from_b_noop] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(""" Rules with errors: 1 (A, [B], a_from_b_noop()): No rule was available to compute B with parameter type SubA """).strip(), str(cm.exception))
def test_ruleset_with_ambiguity(self): @rule def a_from_b_and_c(b: B, c: C) -> A: pass @rule def a_from_c_and_b(c: C, b: B) -> A: pass @rule def d_from_a(a: A) -> D: pass rules = [ a_from_b_and_c, a_from_c_and_b, RootRule(B), RootRule(C), # TODO: Without a rule triggering the selection of A, we don't detect ambiguity here. d_from_a, ] with self.assertRaises(Exception) as cm: create_scheduler(rules) assert_equal_graph_output( self, dedent(f"""\ Rules with errors: 3 {fmt_rule(a_from_b_and_c)}: Was not reachable, either because no rules could produce the params or because it was shadowed by another @rule. {fmt_rule(a_from_c_and_b)}: Was not reachable, either because no rules could produce the params or because it was shadowed by another @rule. {fmt_rule(d_from_a)}: Ambiguous rules to compute A with parameter types (B, C): {fmt_graph_rule(a_from_b_and_c)} for (B, C) {fmt_graph_rule(a_from_c_and_b)} for (B, C) """), str(cm.exception), )
def test_ruleset_with_missing_product_type(self): @rule def a_from_b(b: B) -> A: pass rules = [RootRule(SubA), a_from_b] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(f"""\ Rules with errors: 1 {fmt_rule(a_from_b)}: No rule was available to compute B with parameter type SubA """).strip(), str(cm.exception), )
def create_subgraph(self, requested_product, rules, subject, validate=True): scheduler = create_scheduler(rules + _suba_root_rules, validate=validate) return "\n".join( scheduler.rule_subgraph_visualization([type(subject)], requested_product))
def test_not_fulfillable_duplicated_dependency(self): # If a rule depends on another rule+subject in two ways, and one of them is unfulfillable # Only the unfulfillable one should be in the errors. @rule def a_from_c(c: C) -> A: pass @rule def b_from_d(d: D) -> B: pass @rule async def d_from_a_and_suba(a: A, suba: SubA) -> D: # type: ignore[return] _ = await Get[A](C, C()) # noqa: F841 rules = _suba_root_rules + [ a_from_c, b_from_d, d_from_a_and_suba, ] with self.assertRaises(Exception) as cm: create_scheduler(rules) assert_equal_graph_output( self, dedent(f"""\ Rules with errors: 3 {fmt_rule(a_from_c)}: Was not reachable, either because no rules could produce the params or because it was shadowed by another @rule. {fmt_rule(b_from_d)}: No rule was available to compute D with parameter type SubA {fmt_rule(d_from_a_and_suba, gets=[("A", "C")])}: No rule was available to compute A with parameter type SubA """).strip(), str(cm.exception), )
def test_ruleset_with_ambiguity(self): @rule def a_from_c_and_b(c: C, b: B) -> A: pass @rule def a_from_b_and_c(b: B, c: C) -> A: pass @rule def d_from_a(a: A) -> D: pass rules = [ a_from_c_and_b, a_from_b_and_c, RootRule(B), RootRule(C), # TODO: Without a rule triggering the selection of A, we don't detect ambiguity here. d_from_a, ] with self.assertRaises(Exception) as cm: create_scheduler(rules) self.assert_equal_with_printing( dedent(""" Rules with errors: 3 (A, [B, C], a_from_b_and_c()): Was not usable by any other @rule. (A, [C, B], a_from_c_and_b()): Was not usable by any other @rule. (D, [A], d_from_a()): Ambiguous rules to compute A with parameter types (B+C): (A, [B, C], a_from_b_and_c()) for (B+C) (A, [C, B], a_from_c_and_b()) for (B+C) """).strip(), str(cm.exception))
def create_full_graph(self, rules, validate=True): scheduler = create_scheduler(rules, validate=validate) return "\n".join(scheduler.rule_graph_visualization())