from functools import cached_property
import toml
import sys
+from collections import deque
class BudgetGraphBaseError(Exception):
immediate_children: Set["Node"]
budget_excluding_subtasks: Money
budget_including_subtasks: Money
+ fixed_budget_excluding_subtasks: Money
+ fixed_budget_including_subtasks: Money
nlnet_milestone: Optional[str]
def __init__(self, graph: "BudgetGraph", bug: Bug):
self.parent_id = getattr(bug, "cf_budget_parent", None)
self.immediate_children = set()
self.budget_excluding_subtasks = Money.from_str(bug.cf_budget)
+ self.fixed_budget_excluding_subtasks = self.budget_excluding_subtasks
self.budget_including_subtasks = Money.from_str(bug.cf_total_budget)
+ self.fixed_budget_including_subtasks = self.budget_including_subtasks
self.nlnet_milestone = bug.cf_nlnet_milestone
if self.nlnet_milestone == "---":
self.nlnet_milestone = None
yield from visitor(i)
return visitor(self)
+ def children_breadth_first(self) -> Iterable["Node"]:
+ q = deque(self.immediate_children)
+ while True:
+ try:
+ node = q.popleft()
+ except IndexError:
+ return
+ q.extend(node.immediate_children)
+ yield node
+
def __eq__(self, other):
return self.bug.id == other.bug.id
f"parent={parent}, "
f"budget_excluding_subtasks={self.budget_excluding_subtasks}, "
f"budget_including_subtasks={self.budget_including_subtasks}, "
+ f"fixed_budget_excluding_subtasks={self.fixed_budget_excluding_subtasks}, "
+ f"fixed_budget_including_subtasks={self.fixed_budget_including_subtasks}, "
f"nlnet_milestone={self.nlnet_milestone!r}, "
f"immediate_children={immediate_children!r}, "
f"payees={self.payees!r}")
f" #{self.root_bug_id}")
-class BudgetGraphMoneyMismatch(BudgetGraphError):
+class BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(BudgetGraphError):
def __init__(self, bug_id: int, root_bug_id: int,
expected_budget_excluding_subtasks: Money):
super().__init__(bug_id, root_bug_id)
f" {self.expected_budget_excluding_subtasks}")
+class BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(BudgetGraphError):
+ def __init__(self, bug_id: int, root_bug_id: int,
+ expected_budget_including_subtasks: Money):
+ super().__init__(bug_id, root_bug_id)
+ self.expected_budget_including_subtasks = \
+ expected_budget_including_subtasks
+
+ def __str__(self):
+ return (f"Budget assigned to task including subtasks "
+ f"(cf_total_budget field) doesn't match calculated value: "
+ f"bug #{self.bug_id}, calculated value"
+ f" {self.expected_budget_including_subtasks}")
+
+
class BudgetGraphNegativeMoney(BudgetGraphError):
def __str__(self):
return (f"Budget assigned to task is less than zero: "
class BudgetGraphPayeesMoneyMismatch(BudgetGraphError):
- def __init__(self, bug_id: int, root_bug_id: int, payees_total: Money):
+ def __init__(self, bug_id: int, root_bug_id: int, payees_total: Money,
+ expected_payees_total: Money):
super().__init__(bug_id, root_bug_id)
self.payees_total = payees_total
+ self.expected_payees_total = expected_payees_total
def __str__(self):
- return (f"Budget assigned to task excluding subtasks "
- f"(cf_budget field) doesn't match total value "
- f"assigned to payees (cf_payees_list): "
- f"bug #{self.bug_id}, calculated total"
- f" {self.payees_total}")
+ return (f"Total budget assigned to payees (cf_payees_list) doesn't "
+ f"match expected value: bug #{self.bug_id}, calculated total "
+ f"{self.payees_total}, expected value "
+ f"{self.expected_payees_total}")
class BudgetGraphNegativePayeeMoney(BudgetGraphError):
or node.budget_excluding_subtasks != 0:
errors.append(BudgetGraphMoneyWithNoMilestone(
node.bug.id, root.bug.id))
+
if node.nlnet_milestone != root.nlnet_milestone:
errors.append(BudgetGraphMilestoneMismatch(
node.bug.id, root.bug.id))
+
if node.budget_excluding_subtasks < 0 \
or node.budget_including_subtasks < 0:
errors.append(BudgetGraphNegativeMoney(
node.bug.id, root.bug.id))
- budget = node.budget_including_subtasks
+
+ subtasks_total = Money(0)
for child in node.immediate_children:
- budget -= child.budget_including_subtasks
- if node.budget_excluding_subtasks != budget:
- errors.append(BudgetGraphMoneyMismatch(
- node.bug.id, root.bug.id, budget))
+ subtasks_total += child.fixed_budget_including_subtasks
+
payees_total = Money(0)
for payee_key, payee_value in node.payees.items():
if payee_value < 0:
errors.append(BudgetGraphNegativePayeeMoney(
node.bug.id, root.bug.id, payee_key))
payees_total += payee_value
- if node.budget_excluding_subtasks != payees_total \
- and len(node.payees) != 0:
+
+ def set_including_from_excluding_and_error():
+ node.fixed_budget_including_subtasks = \
+ node.budget_excluding_subtasks + subtasks_total
+ errors.append(
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ node.bug.id, root.bug.id,
+ node.fixed_budget_including_subtasks))
+
+ def set_including_from_payees_and_error():
+ node.fixed_budget_including_subtasks = \
+ payees_total + subtasks_total
+ errors.append(
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ node.bug.id, root.bug.id,
+ node.fixed_budget_including_subtasks))
+
+ def set_excluding_from_including_and_error():
+ node.fixed_budget_excluding_subtasks = \
+ node.budget_including_subtasks - subtasks_total
+ errors.append(
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ node.bug.id, root.bug.id,
+ node.fixed_budget_excluding_subtasks))
+
+ def set_excluding_from_payees_and_error():
+ node.fixed_budget_excluding_subtasks = \
+ payees_total
+ errors.append(
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ node.bug.id, root.bug.id,
+ node.fixed_budget_excluding_subtasks))
+
+ def set_payees_from_including_and_error():
+ fixed_payees_total = \
+ node.budget_including_subtasks - subtasks_total
errors.append(BudgetGraphPayeesMoneyMismatch(
- node.bug.id, root.bug.id, payees_total))
+ node.bug.id, root.bug.id, payees_total, fixed_payees_total))
+
+ def set_payees_from_excluding_and_error():
+ fixed_payees_total = \
+ node.budget_excluding_subtasks
+ errors.append(BudgetGraphPayeesMoneyMismatch(
+ node.bug.id, root.bug.id, payees_total, fixed_payees_total))
+
+ payees_matches_including = \
+ node.budget_including_subtasks - subtasks_total == payees_total
+ payees_matches_excluding = \
+ node.budget_excluding_subtasks == payees_total
+ including_matches_excluding = \
+ node.budget_including_subtasks - subtasks_total \
+ == node.budget_excluding_subtasks
+
+ if payees_matches_including \
+ and payees_matches_excluding \
+ and including_matches_excluding:
+ pass # no error
+ elif payees_matches_including:
+ # can't have 2 match without all 3 matching
+ assert not payees_matches_excluding
+ assert not including_matches_excluding
+ if node.budget_including_subtasks == 0 and len(node.payees) == 0:
+ set_including_from_excluding_and_error()
+ else:
+ set_excluding_from_including_and_error()
+ elif payees_matches_excluding:
+ # can't have 2 match without all 3 matching
+ assert not payees_matches_including
+ assert not including_matches_excluding
+ if node.budget_excluding_subtasks == 0 and len(node.payees) == 0:
+ if node.budget_including_subtasks == 0:
+ set_including_from_excluding_and_error()
+ else:
+ set_excluding_from_including_and_error()
+ else:
+ set_including_from_excluding_and_error()
+ elif including_matches_excluding:
+ # can't have 2 match without all 3 matching
+ assert not payees_matches_including
+ assert not payees_matches_excluding
+ if len(node.payees) == 0:
+ pass # no error -- payees is just not set
+ elif node.budget_excluding_subtasks == 0 \
+ and node.budget_including_subtasks == 0:
+ set_excluding_from_payees_and_error()
+ set_including_from_payees_and_error()
+ else:
+ set_payees_from_excluding_and_error()
+ else:
+ # nothing matches
+ if len(node.payees) == 0:
+ # payees unset -- don't need to set payees
+ if node.budget_including_subtasks == 0:
+ set_including_from_excluding_and_error()
+ else:
+ set_excluding_from_including_and_error()
+ elif node.budget_excluding_subtasks == 0 \
+ and node.budget_including_subtasks == 0:
+ set_excluding_from_payees_and_error()
+ set_including_from_payees_and_error()
+ elif node.budget_excluding_subtasks == 0:
+ set_excluding_from_including_and_error()
+ set_payees_from_including_and_error()
+ elif node.budget_including_subtasks == 0:
+ set_including_from_excluding_and_error()
+ set_payees_from_excluding_and_error()
+ else:
+ set_including_from_excluding_and_error()
+ set_payees_from_excluding_and_error()
def get_errors(self) -> List[BudgetGraphBaseError]:
errors = []
for root in roots:
try:
- self._get_node_errors(root, root, errors)
- for child in root.children():
+ for child in reversed(list(root.children_breadth_first())):
try:
self._get_node_errors(root, child, errors)
except BudgetGraphBaseError as e:
errors.append(e)
+ self._get_node_errors(root, root, errors)
except BudgetGraphBaseError as e:
errors.append(e)
return errors
from budget_sync.budget_graph import (BudgetGraphLoopError, BudgetGraph,
Node, BudgetGraphMoneyWithNoMilestone,
BudgetGraphBaseError,
- BudgetGraphMoneyMismatch,
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks,
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks,
BudgetGraphNegativeMoney,
BudgetGraphMilestoneMismatch,
BudgetGraphNegativePayeeMoney,
"bug #1, root bug #5")
def test_budget_graph_money_mismatch(self):
- self.assertEqual(str(BudgetGraphMoneyMismatch(1, 5, "123.4")),
- "Budget assigned to task excluding subtasks "
- "(cf_budget field) doesn't match calculated value:"
- " bug #1, calculated value 123.4")
+ self.assertEqual(str(
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 5, "123.4")),
+ "Budget assigned to task excluding subtasks "
+ "(cf_budget field) doesn't match calculated value:"
+ " bug #1, calculated value 123.4")
+ self.assertEqual(str(
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 5, "123.4")),
+ "Budget assigned to task including subtasks "
+ "(cf_total_budget field) doesn't match calculated value:"
+ " bug #1, calculated value 123.4")
def test_budget_graph_negative_money(self):
self.assertEqual(str(BudgetGraphNegativeMoney(1, 5)),
def test_budget_graph_payees_money_mismatch(self):
self.assertEqual(str(
- BudgetGraphPayeesMoneyMismatch(1, 5, Money(123))),
- "Budget assigned to task excluding subtasks (cf_budget field) "
- "doesn't match total value assigned to payees (cf_payees_list):"
- " bug #1, calculated total 123")
+ BudgetGraphPayeesMoneyMismatch(1, 5, Money(123), Money(456))),
+ "Total budget assigned to payees (cf_payees_list) doesn't match "
+ "expected value: bug #1, calculated total 123, expected value 456")
EXAMPLE_BUG1 = MockBug(bug_id=1,
self.assertEqual(node1.budget_including_subtasks, Money(cents=2000))
self.assertEqual(node1.nlnet_milestone, "abc")
self.assertEqual(list(node1.children()), [node2])
+ self.assertEqual(list(node1.children_breadth_first()), [node2])
self.assertEqual(node1.payees, {})
self.assertIsInstance(node2, Node)
self.assertIs(node2.graph, bg)
self.assertEqual(node2.nlnet_milestone, "abc")
self.assertEqual(node2.payees, {})
- def test_money_with_no_milestone(self):
+ def test_children(self):
bg = BudgetGraph([
MockBug(bug_id=1,
cf_budget_parent=None,
cf_budget="0",
- cf_total_budget="10",
+ cf_total_budget="0",
cf_nlnet_milestone=None,
cf_payees_list=""),
- ])
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphMoneyWithNoMilestone,
- BudgetGraphMoneyMismatch])
- self.assertEqual(errors[0].bug_id, 1)
- self.assertEqual(errors[0].root_bug_id, 1)
- bg = BudgetGraph([
- MockBug(bug_id=1,
- cf_budget_parent=None,
- cf_budget="10",
+ MockBug(bug_id=2,
+ cf_budget_parent=1,
+ cf_budget="0",
cf_total_budget="0",
cf_nlnet_milestone=None,
cf_payees_list=""),
- ])
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphMoneyWithNoMilestone,
- BudgetGraphMoneyMismatch])
- self.assertEqual(errors[0].bug_id, 1)
- self.assertEqual(errors[0].root_bug_id, 1)
- bg = BudgetGraph([
- MockBug(bug_id=1,
- cf_budget_parent=None,
- cf_budget="10",
- cf_total_budget="10",
+ MockBug(bug_id=3,
+ cf_budget_parent=1,
+ cf_budget="0",
+ cf_total_budget="0",
+ cf_nlnet_milestone=None,
+ cf_payees_list=""),
+ MockBug(bug_id=4,
+ cf_budget_parent=1,
+ cf_budget="0",
+ cf_total_budget="0",
+ cf_nlnet_milestone=None,
+ cf_payees_list=""),
+ MockBug(bug_id=5,
+ cf_budget_parent=3,
+ cf_budget="0",
+ cf_total_budget="0",
+ cf_nlnet_milestone=None,
+ cf_payees_list=""),
+ MockBug(bug_id=6,
+ cf_budget_parent=3,
+ cf_budget="0",
+ cf_total_budget="0",
+ cf_nlnet_milestone=None,
+ cf_payees_list=""),
+ MockBug(bug_id=7,
+ cf_budget_parent=5,
+ cf_budget="0",
+ cf_total_budget="0",
cf_nlnet_milestone=None,
cf_payees_list=""),
])
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors, [BudgetGraphMoneyWithNoMilestone])
- self.assertEqual(errors[0].bug_id, 1)
- self.assertEqual(errors[0].root_bug_id, 1)
+ self.assertEqual(len(bg.nodes), 7)
+ node1: Node = bg.nodes[1]
+ node2: Node = bg.nodes[2]
+ node3: Node = bg.nodes[3]
+ node4: Node = bg.nodes[4]
+ node5: Node = bg.nodes[5]
+ node6: Node = bg.nodes[6]
+ node7: Node = bg.nodes[7]
+ self.assertEqual(bg.roots, {node1})
+ self.assertEqual(list(node1.children()),
+ [node2, node3, node5, node7, node6, node4])
+ self.assertEqual(list(node1.children_breadth_first()),
+ [node2, node3, node4, node5, node6, node7])
- def test_money_mismatch(self):
+ def test_money_with_no_milestone(self):
bg = BudgetGraph([
MockBug(bug_id=1,
cf_budget_parent=None,
cf_budget="0",
cf_total_budget="10",
- cf_nlnet_milestone="abc",
+ cf_nlnet_milestone=None,
cf_payees_list=""),
])
errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphMoneyMismatch])
+ self.assertErrorTypesMatches(errors, [
+ BudgetGraphMoneyWithNoMilestone,
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks])
self.assertEqual(errors[0].bug_id, 1)
self.assertEqual(errors[0].root_bug_id, 1)
- self.assertEqual(errors[0].expected_budget_excluding_subtasks, 10)
bg = BudgetGraph([
MockBug(bug_id=1,
cf_budget_parent=None,
cf_budget="10",
cf_total_budget="0",
- cf_nlnet_milestone="abc",
+ cf_nlnet_milestone=None,
cf_payees_list=""),
])
errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphMoneyMismatch])
+ self.assertErrorTypesMatches(errors, [
+ BudgetGraphMoneyWithNoMilestone,
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks])
self.assertEqual(errors[0].bug_id, 1)
self.assertEqual(errors[0].root_bug_id, 1)
- self.assertEqual(errors[0].expected_budget_excluding_subtasks, 0)
bg = BudgetGraph([
MockBug(bug_id=1,
cf_budget_parent=None,
cf_budget="10",
cf_total_budget="10",
- cf_nlnet_milestone="abc",
- cf_payees_list=""),
- ])
- errors = bg.get_errors()
- self.assertEqual(errors, [])
- bg = BudgetGraph([
- MockBug(bug_id=1,
- cf_budget_parent=None,
- cf_budget="10",
- cf_total_budget="10",
- cf_nlnet_milestone="abc",
- cf_payees_list=""),
- MockBug(bug_id=2,
- cf_budget_parent=1,
- cf_budget="10",
- cf_total_budget="10",
- cf_nlnet_milestone="abc",
- cf_payees_list=""),
- MockBug(bug_id=3,
- cf_budget_parent=1,
- cf_budget="1",
- cf_total_budget="10",
- cf_nlnet_milestone="abc",
+ cf_nlnet_milestone=None,
cf_payees_list=""),
])
errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphMoneyMismatch,
- BudgetGraphMoneyMismatch])
+ self.assertErrorTypesMatches(errors, [BudgetGraphMoneyWithNoMilestone])
self.assertEqual(errors[0].bug_id, 1)
self.assertEqual(errors[0].root_bug_id, 1)
- self.assertEqual(errors[0].expected_budget_excluding_subtasks, -10)
- self.assertEqual(errors[1].bug_id, 3)
- self.assertEqual(errors[1].root_bug_id, 1)
- self.assertEqual(errors[1].expected_budget_excluding_subtasks, 10)
+
+ def test_money_mismatch(self):
+ def helper(budget, total_budget, payees_list, child_budget,
+ expected_errors, expected_fixed_error_types=None):
+ if expected_fixed_error_types is None:
+ expected_fixed_error_types = []
+ bg = BudgetGraph([
+ MockBug(bug_id=1,
+ cf_budget_parent=None,
+ cf_budget=budget,
+ cf_total_budget=total_budget,
+ cf_nlnet_milestone="abc",
+ cf_payees_list=payees_list),
+ MockBug(bug_id=2,
+ cf_budget_parent=1,
+ cf_budget=child_budget,
+ cf_total_budget=child_budget,
+ cf_nlnet_milestone="abc",
+ cf_payees_list=""),
+ ])
+ node1: Node = bg.nodes[1]
+ errors = bg.get_errors()
+ self.assertErrorTypesMatches(errors,
+ [type(i) for i in expected_errors])
+ self.assertEqual([str(i) for i in errors],
+ [str(i) for i in expected_errors])
+ bg = BudgetGraph([
+ MockBug(bug_id=1,
+ cf_budget_parent=None,
+ cf_budget=str(node1.fixed_budget_excluding_subtasks),
+ cf_total_budget=str(
+ node1.fixed_budget_including_subtasks),
+ cf_nlnet_milestone="abc",
+ cf_payees_list=payees_list),
+ MockBug(bug_id=2,
+ cf_budget_parent=1,
+ cf_budget=child_budget,
+ cf_total_budget=child_budget,
+ cf_nlnet_milestone="abc",
+ cf_payees_list=""),
+ ])
+ errors = bg.get_errors()
+ self.assertErrorTypesMatches(errors,
+ expected_fixed_error_types)
+ helper(budget="0",
+ total_budget="0",
+ payees_list="",
+ child_budget="0",
+ expected_errors=[])
+ helper(budget="0",
+ total_budget="0",
+ payees_list="",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(5)),
+ ])
+ helper(budget="0",
+ total_budget="0",
+ payees_list="a=1",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(1)),
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(1)),
+ ])
+ helper(budget="0",
+ total_budget="0",
+ payees_list="a=1",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(1)),
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(6)),
+ ])
+ helper(budget="0",
+ total_budget="0",
+ payees_list="a=10",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(10)),
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(10)),
+ ])
+ helper(budget="0",
+ total_budget="0",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(10)),
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(15)),
+ ])
+ helper(budget="0",
+ total_budget="100",
+ payees_list="",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(100)),
+ ])
+ helper(budget="0",
+ total_budget="100",
+ payees_list="",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(95)),
+ ])
+ helper(budget="0",
+ total_budget="100",
+ payees_list="a=1",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(100)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(100)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="0",
+ total_budget="100",
+ payees_list="a=1",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(95)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(95)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="0",
+ total_budget="100",
+ payees_list="a=10",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(100)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(100)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="0",
+ total_budget="100",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(95)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(95)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="0",
+ total_budget="5",
+ payees_list="",
+ child_budget="5",
+ expected_errors=[])
+ helper(budget="0",
+ total_budget="5",
+ payees_list="a=1",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(0)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="0",
+ total_budget="5",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(10), Money(0)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="0",
+ payees_list="",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(10)),
+ ])
+ helper(budget="10",
+ total_budget="0",
+ payees_list="",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(15)),
+ ])
+ helper(budget="10",
+ total_budget="0",
+ payees_list="a=1",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(10)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="0",
+ payees_list="a=1",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(15)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="0",
+ payees_list="a=10",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(10)),
+ ])
+ helper(budget="10",
+ total_budget="0",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(15)),
+ ])
+ helper(budget="10",
+ total_budget="10",
+ payees_list="",
+ child_budget="0",
+ expected_errors=[])
+ helper(budget="10",
+ total_budget="10",
+ payees_list="a=1",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="10",
+ payees_list="a=10",
+ child_budget="0",
+ expected_errors=[])
+ helper(budget="10",
+ total_budget="100",
+ payees_list="",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(100)),
+ ])
+ helper(budget="10",
+ total_budget="100",
+ payees_list="",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money(95)),
+ ])
+ helper(budget="10",
+ total_budget="100",
+ payees_list="a=1",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(10)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="100",
+ payees_list="a=1",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(15)),
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="100",
+ payees_list="a=10",
+ child_budget="0",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(10)),
+ ])
+ helper(budget="10",
+ total_budget="100",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks(
+ 1, 1, Money(15)),
+ ])
+ helper(budget="10",
+ total_budget="15",
+ payees_list="",
+ child_budget="5",
+ expected_errors=[])
+ helper(budget="10",
+ total_budget="15",
+ payees_list="a=1",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphPayeesMoneyMismatch(1, 1, Money(1), Money(10)),
+ ],
+ expected_fixed_error_types=[BudgetGraphPayeesMoneyMismatch])
+ helper(budget="10",
+ total_budget="15",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[])
+
+ helper(budget="1",
+ total_budget="15",
+ payees_list="a=10",
+ child_budget="5",
+ expected_errors=[
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
+ 1, 1, Money("10"))
+ ])
def test_negative_money(self):
bg = BudgetGraph([
cf_payees_list=""),
])
errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphNegativeMoney,
- BudgetGraphMoneyMismatch])
+ self.assertErrorTypesMatches(errors, [
+ BudgetGraphNegativeMoney,
+ BudgetGraphMoneyMismatchForBudgetExcludingSubtasks])
self.assertEqual(errors[0].bug_id, 1)
self.assertEqual(errors[0].root_bug_id, 1)
self.assertEqual(errors[1].bug_id, 1)
cf_payees_list=""),
])
errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphNegativeMoney,
- BudgetGraphMoneyMismatch])
+ self.assertErrorTypesMatches(errors, [
+ BudgetGraphNegativeMoney,
+ BudgetGraphMoneyMismatchForBudgetIncludingSubtasks])
self.assertEqual(errors[0].bug_id, 1)
self.assertEqual(errors[0].root_bug_id, 1)
self.assertEqual(errors[1].bug_id, 1)
self.assertEqual(errors[1].root_bug_id, 1)
- self.assertEqual(errors[1].expected_budget_excluding_subtasks, 0)
+ self.assertEqual(errors[1].expected_budget_including_subtasks, -10)
bg = BudgetGraph([
MockBug(bug_id=1,
cf_budget_parent=None,
self.assertEqual(errors[0].bug_id, 1)
self.assertEqual(errors[0].root_bug_id, 1)
self.assertEqual(errors[0].payees_total, 15)
- bg = BudgetGraph([
- MockBug(bug_id=1,
- cf_budget_parent=None,
- cf_budget="0",
- cf_total_budget="0",
- cf_nlnet_milestone=None,
- cf_payees_list="payee = 5\npayee2 = 10"),
- ])
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphPayeesMoneyMismatch])
- self.assertEqual(errors[0].bug_id, 1)
- self.assertEqual(errors[0].root_bug_id, 1)
- self.assertEqual(errors[0].payees_total, 15)
def test_payees_parse_error(self):
def check_parse_error(cf_payees_list, expected_msg):