from bugzilla.bug import Bug
-from bugzilla import Bugzilla
from typing import Set, Dict, Iterable, Optional, List, Union, Any
-from budget_sync.util import all_bugs
+from budget_sync.util import BugStatus
from budget_sync.money import Money
from budget_sync.config import Config, Person, Milestone
from functools import cached_property
f"#{self.bug_id}: unknown milestone: {self.milestone_str!r}"
+class BudgetGraphUnknownStatus(BudgetGraphParseError):
+ def __init__(self, bug_id: int, status_str: str):
+ super().__init__(bug_id)
+ self.status_str = status_str
+
+ def __str__(self):
+ return f"failed to parse status field of bug " \
+ f"#{self.bug_id}: unknown status: {self.status_str!r}"
+
+
class Node:
graph: "BudgetGraph"
bug: Bug
if self.milestone_str == "---":
self.milestone_str = None
+ @property
+ def status(self) -> BugStatus:
+ try:
+ return BugStatus.cast(self.bug.status)
+ except ValueError:
+ new_err = BudgetGraphUnknownStatus(self.bug.id, self.bug.status)
+ raise new_err.with_traceback(sys.exc_info()[2])
+
@cached_property
def bug_url(self) -> str:
return f"{self.graph.config.bugzilla_url_stripped}/show_bug.cgi?" \
milestone = repr(self.milestone)
except BudgetGraphBaseError:
milestone = "<unknown milestone>"
+ try:
+ status = repr(self.status)
+ except BudgetGraphBaseError:
+ status = f"<unknown status: {self.bug.status!r}>"
immediate_children = []
for i in self.immediate_children:
immediate_children.append(_NodeSimpleReprWrapper(i))
f"milestone_str={self.milestone_str!r}, "
f"milestone={milestone}, "
f"immediate_children={immediate_children!r}, "
- f"payments={payments!r}")
+ f"payments={payments!r}, "
+ f"status={status})")
class BudgetGraphError(BudgetGraphBaseError):
except BudgetGraphBaseError as e:
errors.append(e)
+ try:
+ # check for status errors
+ node.status
+ except BudgetGraphBaseError as e:
+ errors.append(e)
+
if node.milestone_str != root.milestone_str:
errors.append(BudgetGraphMilestoneMismatch(
node.bug.id, root.bug.id))
for payment in node.payments.values():
retval[payment.payee][node.milestone].append(payment)
return retval
+
+ def __repr__(self):
+ nodes = [*self.nodes.values()]
+ try:
+ roots = [_NodeSimpleReprWrapper(i) for i in self.roots]
+ roots.sort()
+ roots_str = repr(roots)
+ except BudgetGraphBaseError:
+ roots_str = "<failed>"
+ return f"BudgetGraph{{nodes={nodes!r}, roots={roots}}}"
BudgetGraphNegativeMoney, BudgetGraphMilestoneMismatch,
BudgetGraphNegativePayeeMoney, BudgetGraphPayeesParseError,
BudgetGraphPayeesMoneyMismatch, BudgetGraphUnknownMilestone,
- BudgetGraphDuplicatePayeesForTask, BudgetGraphIncorrectRootForMilestone)
+ BudgetGraphDuplicatePayeesForTask, BudgetGraphIncorrectRootForMilestone,
+ BudgetGraphUnknownStatus)
from budget_sync.money import Money
+from budget_sync.util import BugStatus
from typing import List, Type
import unittest
"failed to parse cf_nlnet_milestone field of bug "
"#123: unknown milestone: 'fake milestone'")
+ def test_budget_graph_unknown_status(self):
+ self.assertEqual(str(BudgetGraphUnknownStatus(
+ 123, "fake status")),
+ "failed to parse status field of bug "
+ "#123: unknown status: 'fake status'")
+
def test_budget_graph_money_mismatch(self):
self.assertEqual(str(
BudgetGraphMoneyMismatchForBudgetExcludingSubtasks(
error_types.append(type(error))
self.assertEqual(wrap_type_list(error_types), wrap_type_list(template))
+ def test_repr(self):
+ bg = BudgetGraph([EXAMPLE_PARENT_BUG1, EXAMPLE_CHILD_BUG2],
+ EXAMPLE_CONFIG)
+ self.assertEqual(
+ repr(bg),
+ "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
+ "budget_excluding_subtasks=10, budget_including_subtasks=20, "
+ "fixed_budget_excluding_subtasks=10, "
+ "fixed_budget_including_subtasks=20, "
+ "milestone_str='milestone 1', milestone=Milestone(config=..., "
+ "identifier='milestone 1', canonical_bug_id=1), "
+ "immediate_children=[#2], payments=[], "
+ "status=BugStatus.CONFIRMED), Node(graph=..., id=#2, root=#1, "
+ "parent=#1, budget_excluding_subtasks=10, "
+ "budget_including_subtasks=10, "
+ "fixed_budget_excluding_subtasks=10, "
+ "fixed_budget_including_subtasks=10, "
+ "milestone_str='milestone 1', milestone=Milestone(config=..., "
+ "identifier='milestone 1', canonical_bug_id=1), "
+ "immediate_children=[], payments=[], "
+ "status=BugStatus.CONFIRMED)], roots=[#1]}")
+ bg = BudgetGraph([MockBug(bug_id=1, status="blah")],
+ EXAMPLE_CONFIG)
+ self.assertEqual(
+ repr(bg),
+ "BudgetGraph{nodes=[Node(graph=..., id=#1, root=#1, parent=None, "
+ "budget_excluding_subtasks=0, budget_including_subtasks=0, "
+ "fixed_budget_excluding_subtasks=0, "
+ "fixed_budget_including_subtasks=0, milestone_str=None, "
+ "milestone=None, immediate_children=[], payments=[], "
+ "status=<unknown status: 'blah'>)], roots=[#1]}")
+
def test_empty(self):
bg = BudgetGraph([], EXAMPLE_CONFIG)
self.assertEqual(len(bg.nodes), 0)
},
})
+ def test_status(self):
+ bg = BudgetGraph([MockBug(bug_id=1, status="blah")],
+ EXAMPLE_CONFIG)
+ errors = bg.get_errors()
+ self.assertErrorTypesMatches(errors,
+ [BudgetGraphUnknownStatus])
+ self.assertEqual(errors[0].bug_id, 1)
+ self.assertEqual(errors[0].status_str, "blah")
+ for status in BugStatus:
+ bg = BudgetGraph([MockBug(bug_id=1, status=status)],
+ EXAMPLE_CONFIG)
+ self.assertErrorTypesMatches(bg.get_errors(), [])
+ self.assertEqual(bg.nodes[1].status, status)
+
if __name__ == "__main__":
unittest.main()