fixed_budget_excluding_subtasks: Money
fixed_budget_including_subtasks: Money
milestone_str: Optional[str]
- is_in_nlnet_mou: bool
def __init__(self, graph: "BudgetGraph", bug: Bug):
self.graph = graph
@cached_property
def is_in_nlnet_mou(self):
- """returns true if this bugreport is a child of the top-level milestone.
- it does *not* return true for the top-level bugreport itself because
- only the immediate child-nodes comprise the MoU.
+ """returns true if this bugreport is an immediate child of a top-level
+ milestone. it does *not* return true for the top-level bug itself
+ because only the immediate children comprise the MoU.
"""
- if self.parent is None:
- return False
- return self.parent == self.root
+ try:
+ if self.parent is not None and self.milestone is not None:
+ return self.parent.bug.id == self.milestone.canonical_bug_id
+ except BudgetGraphBaseError:
+ pass
+ return False
@cached_property
def closest_bug_in_mou(self) -> Optional["Node"]:
f"#{self.milestone_canonical_bug_id}")
-class BudgetGraphRootWithMilestoneNotInMoU(BudgetGraphError):
- def __init__(self, bug_id: int, milestone: str):
- super().__init__(bug_id, bug_id)
- self.milestone = milestone
-
- def __str__(self):
- return (f"Bug #{self.bug_id} has no parent bug set and has an "
- f"assigned milestone {self.milestone!r} but isn't set "
- f"to be part of the signed MoU")
-
-
-class BudgetGraphInMoUButParentNotInMoU(BudgetGraphError):
- def __init__(self, bug_id: int, parent_bug_id: int, root_bug_id: int,
- milestone: str):
- super().__init__(bug_id, root_bug_id)
- self.parent_bug_id = parent_bug_id
- self.milestone = milestone
-
- def __str__(self):
- return (f"Bug #{self.bug_id} is set to be part of the signed MoU for "
- f"milestone {self.milestone!r}, but its parent bug isn't set "
- f"to be part of the signed MoU")
-
-
-class BudgetGraphInMoUWithoutMilestone(BudgetGraphError):
- def __str__(self):
- return (f"Bug #{self.bug_id} is set to be part of a signed MoU but "
- f"has no milestone set")
-
-
class BudgetGraph:
nodes: Dict[int, Node]
node.bug.id, node.milestone.identifier,
node.milestone.canonical_bug_id
))
- # the root level bugs are not themselves "the child MoU list"
- #elif not node.is_in_nlnet_mou:
- # errors.append(BudgetGraphRootWithMilestoneNotInMoU(
- # node.bug.id, node.milestone_str))
except BudgetGraphBaseError as e:
errors.append(e)
errors.append(BudgetGraphMilestoneMismatch(
node.bug.id, root.bug.id))
- if node.is_in_nlnet_mou:
- if node.milestone_str is None:
- errors.append(BudgetGraphInMoUWithoutMilestone(node.bug.id,
- root.bug.id))
- # don't consider the top-level root to be part of the MoU
- #elif node.parent is not None and \
- # not node.parent.is_in_nlnet_mou:
- # errors.append(BudgetGraphInMoUButParentNotInMoU(
- # node.bug.id, node.parent.bug.id, root.bug.id,
- # node.milestone_str))
-
if node.budget_excluding_subtasks < 0 \
or node.budget_including_subtasks < 0:
errors.append(BudgetGraphNegativeMoney(
BudgetGraphNegativePayeeMoney, BudgetGraphPayeesParseError,
BudgetGraphPayeesMoneyMismatch, BudgetGraphUnknownMilestone,
BudgetGraphIncorrectRootForMilestone,
- BudgetGraphUnknownStatus, BudgetGraphUnknownAssignee,
- BudgetGraphRootWithMilestoneNotInMoU, BudgetGraphInMoUButParentNotInMoU,
- BudgetGraphInMoUWithoutMilestone)
+ BudgetGraphUnknownStatus, BudgetGraphUnknownAssignee)
from budget_sync.money import Money
from budget_sync.util import BugStatus
from typing import List, Type
"Total budget assigned to payees (cf_payees_list) doesn't match "
"expected value: bug #1, calculated total 123, expected value 456")
- def test_budget_graph_root_with_milestone_not_in_mou(self):
- self.assertEqual(str(
- BudgetGraphRootWithMilestoneNotInMoU(1, "milestone 1")),
- "Bug #1 has no parent bug set and has an assigned milestone "
- "'milestone 1' but isn't set to be part of the signed MoU")
-
- def test_budget_graph_in_mou_but_parent_not_in_mou(self):
- self.assertEqual(str(
- BudgetGraphInMoUButParentNotInMoU(5, 3, 1, "milestone 1")),
- "Bug #5 is set to be part of the signed MoU for milestone "
- "'milestone 1', but its parent bug isn't set to be part of "
- "the signed MoU")
-
- def test_budget_graph_in_mou_without_milestone(self):
- self.assertEqual(str(
- BudgetGraphInMoUWithoutMilestone(1, 5)),
- "Bug #1 is set to be part of a signed MoU but has no "
- "milestone set")
-
EXAMPLE_BUG1 = MockBug(bug_id=1,
cf_budget_parent=None,
cf_total_budget="20",
cf_nlnet_milestone="milestone 1",
cf_payees_list="",
- summary="",
- cf_is_in_nlnet_mou2="Yes")
+ summary="")
EXAMPLE_CHILD_BUG2 = MockBug(bug_id=2,
cf_budget_parent=1,
cf_budget="10",
"budget_excluding_subtasks=10, budget_including_subtasks=20, "
"fixed_budget_excluding_subtasks=10, "
"fixed_budget_including_subtasks=20, milestone_str='milestone "
- "1', is_in_nlnet_mou=True, "
+ "1', is_in_nlnet_mou=False, "
"milestone=Milestone(config=..., identifier='milestone 1', "
"canonical_bug_id=1), immediate_children=[#2], payments=[], "
"status=BugStatus.CONFIRMED, assignee=Person<'person3'>, "
"budget_including_subtasks=10, "
"fixed_budget_excluding_subtasks=10, "
"fixed_budget_including_subtasks=10, milestone_str='milestone "
- "1', is_in_nlnet_mou=False, "
+ "1', is_in_nlnet_mou=True, "
"milestone=Milestone(config=..., identifier='milestone 1', "
"canonical_bug_id=1), immediate_children=[], payments=[], "
"status=BugStatus.CONFIRMED, assignee=Person<'person3'>, "
cf_total_budget=total_budget,
cf_nlnet_milestone="milestone 1",
cf_payees_list=payees_list,
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
MockBug(bug_id=2,
cf_budget_parent=1,
cf_budget=child_budget,
node1.fixed_budget_including_subtasks),
cf_nlnet_milestone="milestone 1",
cf_payees_list=payees_list,
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
MockBug(bug_id=2,
cf_budget_parent=1,
cf_budget=child_budget,
cf_total_budget="-10",
cf_nlnet_milestone="milestone 1",
cf_payees_list="",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors, [
cf_total_budget="0",
cf_nlnet_milestone="milestone 1",
cf_payees_list="",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors, [
cf_total_budget="-10",
cf_nlnet_milestone="milestone 1",
cf_payees_list="",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors,
cf_total_budget="0",
cf_nlnet_milestone="milestone 1",
cf_payees_list=cf_payees_list,
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
self.assertErrorTypesMatches(bg.get_errors(), error_types)
self.assertEqual(len(bg.nodes), 1)
cf_total_budget="10",
cf_nlnet_milestone="milestone 1",
cf_payees_list="person1 = 5\nperson2 = 10",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors,
cf_total_budget="0",
cf_nlnet_milestone="milestone 1",
cf_payees_list=cf_payees_list,
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG).get_errors()
self.assertErrorTypesMatches(errors,
[BudgetGraphPayeesParseError])
cf_total_budget="10",
cf_nlnet_milestone="milestone 1",
cf_payees_list="""person1 = -10""",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors,
person1 = 5
alias1 = 5
""",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors, [])
cf_total_budget="10",
cf_nlnet_milestone="milestone 2",
cf_payees_list="",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors,
cf_total_budget="0",
cf_nlnet_milestone="milestone 2",
cf_payees_list="",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
self.assertErrorTypesMatches(errors, [])
cf_total_budget="10",
cf_nlnet_milestone="milestone 1",
cf_payees_list="person1 = 3\nperson2 = 7",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
MockBug(bug_id=2,
cf_budget_parent=None,
cf_budget="10",
cf_total_budget="10",
cf_nlnet_milestone="milestone 2",
cf_payees_list="person3 = 5\nperson2 = 5",
- summary="",
- cf_is_in_nlnet_mou2="Yes"),
+ summary=""),
], EXAMPLE_CONFIG)
self.assertErrorTypesMatches(bg.get_errors(), [])
person1 = EXAMPLE_CONFIG.people["person1"]
def test_closest_bug_in_mou(self):
bg = BudgetGraph([
- MockBug(bug_id=1, cf_nlnet_milestone="milestone 1",
- cf_is_in_nlnet_mou2="Yes"),
+ MockBug(bug_id=1, cf_nlnet_milestone="milestone 1"),
MockBug(bug_id=2, cf_budget_parent=1,
- cf_nlnet_milestone="milestone 1",
- cf_is_in_nlnet_mou2="Yes"),
+ cf_nlnet_milestone="milestone 1"),
MockBug(bug_id=3, cf_budget_parent=2,
- cf_nlnet_milestone="milestone 1",
- cf_is_in_nlnet_mou2="Yes"),
- MockBug(bug_id=4, cf_budget_parent=2,
+ cf_nlnet_milestone="milestone 1"),
+ MockBug(bug_id=4, cf_budget_parent=1,
cf_nlnet_milestone="milestone 1"),
MockBug(bug_id=5, cf_budget_parent=4,
cf_nlnet_milestone="milestone 1"),
MockBug(bug_id=6),
+ MockBug(bug_id=7, cf_nlnet_milestone="bad milestone"),
+ MockBug(bug_id=8, cf_budget_parent=7,
+ cf_nlnet_milestone="bad milestone"),
], EXAMPLE_CONFIG)
errors = bg.get_errors()
- self.assertErrorTypesMatches(errors, [])
- self.assertEqual(bg.nodes[1].closest_bug_in_mou, bg.nodes[1])
+ self.assertErrorTypesMatches(errors, [BudgetGraphUnknownMilestone,
+ BudgetGraphUnknownMilestone])
+ self.assertEqual(bg.nodes[1].closest_bug_in_mou, None)
self.assertEqual(bg.nodes[2].closest_bug_in_mou, bg.nodes[2])
- self.assertEqual(bg.nodes[3].closest_bug_in_mou, bg.nodes[3])
- self.assertEqual(bg.nodes[4].closest_bug_in_mou, bg.nodes[2])
- self.assertEqual(bg.nodes[5].closest_bug_in_mou, bg.nodes[2])
+ self.assertEqual(bg.nodes[3].closest_bug_in_mou, bg.nodes[2])
+ self.assertEqual(bg.nodes[4].closest_bug_in_mou, bg.nodes[4])
+ self.assertEqual(bg.nodes[5].closest_bug_in_mou, bg.nodes[4])
self.assertEqual(bg.nodes[6].closest_bug_in_mou, None)
-
- def test_root_with_milestone_not_in_mou(self):
- bg = BudgetGraph([
- MockBug(bug_id=1, cf_nlnet_milestone="milestone 1"),
- ], EXAMPLE_CONFIG)
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphRootWithMilestoneNotInMoU])
- self.assertEqual(errors[0].bug_id, 1)
- self.assertEqual(errors[0].root_bug_id, 1)
- self.assertEqual(errors[0].milestone, "milestone 1")
-
- def test_budget_graph_in_mou_without_milestone(self):
- bg = BudgetGraph([
- MockBug(bug_id=1, cf_is_in_nlnet_mou2="Yes"),
- ], EXAMPLE_CONFIG)
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphInMoUWithoutMilestone])
- self.assertEqual(errors[0].bug_id, 1)
- self.assertEqual(errors[0].root_bug_id, 1)
-
- def test_in_mou_but_parent_not_in_mou(self):
- bg = BudgetGraph([
- MockBug(bug_id=1, cf_nlnet_milestone="milestone 1",
- cf_is_in_nlnet_mou2="Yes"),
- MockBug(bug_id=2, cf_nlnet_milestone="milestone 1",
- cf_budget_parent=1),
- MockBug(bug_id=3, cf_nlnet_milestone="milestone 1",
- cf_budget_parent=2, cf_is_in_nlnet_mou2="Yes"),
- ], EXAMPLE_CONFIG)
- errors = bg.get_errors()
- self.assertErrorTypesMatches(errors,
- [BudgetGraphInMoUButParentNotInMoU])
- self.assertEqual(errors[0].bug_id, 3)
- self.assertEqual(errors[0].root_bug_id, 1)
- self.assertEqual(errors[0].parent_bug_id, 2)
+ self.assertEqual(bg.nodes[7].closest_bug_in_mou, None)
+ self.assertEqual(bg.nodes[8].closest_bug_in_mou, None)
if __name__ == "__main__":