diff --git a/src/rust/engine/src/rule_graph.rs b/src/rust/engine/src/rule_graph.rs index be0cc0faa78..9010a72ac57 100644 --- a/src/rust/engine/src/rule_graph.rs +++ b/src/rust/engine/src/rule_graph.rs @@ -1000,10 +1000,11 @@ impl RuleGraph { diagnostics.dedup_by(|l, r| l.reason == r.reason); let errors = diagnostics .into_iter() - .map(|d| { + .map(|mut d| { if d.details.is_empty() { d.reason.clone() } else { + d.details.sort(); format!("{}:\n {}", d.reason, d.details.join("\n ")) } }) diff --git a/tests/python/pants_test/engine/test_rules.py b/tests/python/pants_test/engine/test_rules.py index c473a122130..aaaae549a34 100644 --- a/tests/python/pants_test/engine/test_rules.py +++ b/tests/python/pants_test/engine/test_rules.py @@ -78,6 +78,27 @@ def test_ruleset_with_missing_product_type(self): """).strip(), str(cm.exception)) + def test_ruleset_with_ambiguity(self): + rules = [ + TaskRule(A, [Select(C), Select(B)], noop), + TaskRule(A, [Select(B), Select(C)], noop), + RootRule(B), + RootRule(C), + # TODO: Without a rule triggering the selection of A, we don't detect ambiguity here. + TaskRule(D, [Select(A)], noop), + ] + with self.assertRaises(Exception) as cm: + create_scheduler(rules) + + self.assert_equal_with_printing(dedent(""" + Rules with errors: 1 + (D, [Select(A)], noop): + ambiguous rules for Select(A) with parameter types (B+C): + (A, [Select(B), Select(C)], noop) for (B+C) + (A, [Select(C), Select(B)], noop) for (B+C) + """).strip(), + str(cm.exception)) + def test_ruleset_with_rule_with_two_missing_selects(self): rules = _suba_root_rules + [TaskRule(A, [Select(B), Select(C)], noop)] with self.assertRaises(Exception) as cm: