From 0ab88ccd9bdd31315e52ca2ff5aefbbc6c798459 Mon Sep 17 00:00:00 2001 From: Luke Meyer Date: Thu, 3 Aug 2023 16:42:16 -0400 Subject: [PATCH] [ART-5863] manage early kernel tracker completion * add logic both when searching for bugs to clone and when closing them out to also close, comment, and link advisories for related KMAINT trackers * expand --comment flag to --update-tracker flag * extract/expand more common logic to early_kernel.py --- elliottlib/cli/find_bugs_kernel_cli.py | 61 ++++++------ elliottlib/cli/find_bugs_kernel_clones_cli.py | 89 +++++------------- elliottlib/early_kernel.py | 94 ++++++++++++++++++- tests/test_find_bugs_kernel_cli.py | 59 +++++++----- tests/test_find_bugs_kernel_clones_cli.py | 26 +++-- 5 files changed, 191 insertions(+), 138 deletions(-) diff --git a/elliottlib/cli/find_bugs_kernel_cli.py b/elliottlib/cli/find_bugs_kernel_cli.py index 4d26ce30..e34931f7 100644 --- a/elliottlib/cli/find_bugs_kernel_cli.py +++ b/elliottlib/cli/find_bugs_kernel_cli.py @@ -25,13 +25,13 @@ def _search_issues(jira_client, *args, **kwargs): class FindBugsKernelCli: def __init__(self, runtime: Runtime, trackers: Sequence[str], - clone: bool, reconcile: bool, comment: bool, dry_run: bool): + clone: bool, reconcile: bool, update_tracker: bool, dry_run: bool): self._runtime = runtime self._logger = runtime.logger self.trackers = list(trackers) self.clone = clone self.reconcile = reconcile - self.comment = comment + self.update_tracker = update_tracker self.dry_run = dry_run self._id_bugs: Dict[int, Bug] = {} # cache for kernel bug; key is bug_id, value is Bug object self._tracker_map: Dict[int, Issue] = {} # bug_id -> KMAINT jira mapping @@ -57,17 +57,17 @@ async def run(self): # Getting KMAINT trackers trackers_keys = self.trackers trackers: List[Issue] = [] - if not trackers_keys: - logger.info("Searching for open trackers...") - trackers = self._find_kmaint_trackers(jira_client, config.tracker_jira.project, config.tracker_jira.labels) - trackers_keys = [t.key for t in trackers] - logger.info("Found %s tracker(s): %s", len(trackers_keys), trackers_keys) - else: + if trackers_keys: logger.info("Find kernel bugs linked from KMAINT tracker(s): %s", trackers_keys) for key in trackers_keys: logger.info("Getting tracker JIRA %s...", key) tracker = jira_client.issue(key) trackers.append(tracker) + else: + logger.info("Searching for open trackers...") + trackers = self._find_kmaint_trackers(jira_client, config.tracker_jira.project, config.tracker_jira.labels) + trackers_keys = [t.key for t in trackers] + logger.info("Found %s tracker(s): %s", len(trackers_keys), trackers_keys) # Get kernel bugs linked from KMAINT trackers report: Dict[str, Any] = {"kernel_bugs": []} @@ -86,9 +86,8 @@ async def run(self): "summary": bug.summary, "tracker": tracker, }) - if self.comment: - logger.info("Checking if making a comment on tracker %s is needed...", tracker.key) - self._comment_on_tracker(jira_client, tracker, koji_api, config.target_jira) + if self.update_tracker: + self._update_tracker(jira_client, tracker, koji_api, config.target_jira) if self.clone and self._id_bugs: # Clone kernel bugs into OCP Jira @@ -175,7 +174,7 @@ def _clone_bugs(self, jira_client: JIRA, bugs: Sequence[Bug], conf: KernelBugSwe jira_client.create_issue_link("Blocks", issue.key, kmaint_tracker) result[bug_id] = [issue] else: - logger.warning("[DRY RUN] Would have created Jira for bug %s", bug_id) + logger.info("[DRY RUN] Would have created Jira for bug %s", bug_id) else: # this bug is already cloned into OCP Jira logger.info("Bug %s is already cloned into OCP: %s", bug_id, [issue.key for issue in found_issues]) result[bug_id] = found_issues @@ -190,7 +189,7 @@ def _clone_bugs(self, jira_client: JIRA, bugs: Sequence[Bug], conf: KernelBugSwe if not self.dry_run: issue.update(fields) else: - logger.warning("[DRY RUN] Would have updated Jira %s to match bug %s", issue.key, bug_id) + logger.info("[DRY RUN] Would have updated Jira %s to match bug %s", issue.key, bug_id) return result @@ -204,30 +203,24 @@ def _print_report(report: Dict, out: TextIO): text = f"{bug['tracker']}\t{bug['id']}\t{'N/A' if not cloned_issues else ','.join(cloned_issues)}\t{bug['status']}\t{bug['summary']}" print_func(text, file=out) - def _comment_on_tracker(self, jira_client: JIRA, tracker: Issue, koji_api: koji.ClientSession, - conf: KernelBugSweepConfig.TargetJiraConfig): + def _update_tracker(self, jira_client: JIRA, tracker: Issue, koji_api: koji.ClientSession, + conf: KernelBugSweepConfig.TargetJiraConfig): logger = self._runtime.logger + logger.info("Checking if an update to tracker %s is needed...", tracker.key) # Determine which NVRs have the fix. e.g. ["kernel-5.14.0-284.14.1.el9_2"] nvrs, candidate, shipped = early_kernel.get_tracker_builds_and_tags(logger, tracker, koji_api, conf) - tracker_message = None if shipped: - tracker_message = f"Build(s) {nvrs} was/were already shipped and tagged into {shipped}." + early_kernel.process_shipped_tracker(logger, self.dry_run, jira_client, tracker, nvrs, shipped) elif candidate: - tracker_message = f"Build(s) {nvrs} was/were already tagged into {candidate}." - if not tracker_message: - logger.info("No need to make a comment on %s", tracker.key) - return - comments = jira_client.comments(tracker.key) - if any(map(lambda comment: comment.body == tracker_message, comments)): - logger.info("A comment was already made on %s", tracker.key) - return - logger.info("Making a comment on tracker %s", tracker.key) - if not self.dry_run: - jira_client.add_comment(tracker.key, tracker_message) - logger.info("Left a comment on tracker %s", tracker.key) + early_kernel.comment_on_tracker( + logger, self.dry_run, jira_client, tracker, + [f"Build(s) {nvrs} was/were already tagged into {candidate}."] + # do not reword, see NOTE in method + ) else: - logger.warning("[DRY RUN] Would have left a comment on tracker %s", tracker.key) + logger.info("No need to update tracker %s", tracker.key) + return @staticmethod def _new_jira_fields_from_bug(bug: Bug, ocp_target_version: str, kmaint_tracker: Optional[str], conf: KernelBugSweepConfig.TargetJiraConfig): @@ -294,10 +287,10 @@ def _new_jira_fields_from_bug(bug: Bug, ocp_target_version: str, kmaint_tracker: is_flag=True, default=False, help="Update summary, description, etc for already cloned Jira bugs. Must be used with --clone") -@click.option("--comment", +@click.option("--update-tracker", is_flag=True, default=False, - help="Make comments on KMAINT trackers") + help="Update KMAINT trackers state, links, and comments") @click.option("--dry-run", is_flag=True, default=False, @@ -306,7 +299,7 @@ def _new_jira_fields_from_bug(bug: Bug, ocp_target_version: str, kmaint_tracker: @click_coroutine async def find_bugs_kernel_cli( runtime: Runtime, trackers: Tuple[str, ...], clone: bool, - reconcile: bool, comment: bool, dry_run: bool): + reconcile: bool, update_tracker: bool, dry_run: bool): """Find kernel bugs in Bugzilla for weekly kernel release through OCP. Example 1: Find kernel bugs and print them out @@ -327,7 +320,7 @@ async def find_bugs_kernel_cli( trackers=trackers, clone=clone, reconcile=reconcile, - comment=comment, + update_tracker=update_tracker, dry_run=dry_run ) await cli.run() diff --git a/elliottlib/cli/find_bugs_kernel_clones_cli.py b/elliottlib/cli/find_bugs_kernel_clones_cli.py index 96a88d6e..153ecb33 100644 --- a/elliottlib/cli/find_bugs_kernel_clones_cli.py +++ b/elliottlib/cli/find_bugs_kernel_clones_cli.py @@ -25,19 +25,19 @@ class FindBugsKernelClonesCli: def __init__(self, runtime: Runtime, trackers: Sequence[str], bugs: Sequence[str], - move: bool, comment: bool, dry_run: bool): + move: bool, update_tracker: bool, dry_run: bool): self._runtime = runtime self._logger = runtime.logger self.trackers = list(trackers) self.bugs = list(bugs) self.move = move - self.comment = comment + self.update_tracker = update_tracker self.dry_run = dry_run def run(self): logger = self._logger - if self.comment and not self.move: - raise ElliottFatalError("--comment must be used with --move") + if self.update_tracker and not self.move: + raise ElliottFatalError("--update-tracker must be used with --move") if self._runtime.assembly_type is not AssemblyTypes.STREAM: raise ElliottFatalError("This command only supports stream assembly.") group_config = self._runtime.group_config @@ -140,7 +140,7 @@ def _find_trackers_for_bugs(self, tracker_bugs.setdefault(tracker.key, []).append(bug) return trackers, tracker_bugs - def _process_shipped_bugs(self, logger, bug_keys, bugs, jira_client: JIRA, nvrs, prod_brew_tag) -> str: + def _process_shipped_bugs(self, logger, bug_keys, bugs, jira_client: JIRA, nvrs, prod_brew_tag): # when NVRs are shipped, ensure the associated bugs are closed with a comment logger.info("Build(s) %s shipped (tagged into %s). Moving bug Jira(s) %s to CLOSED...", nvrs, prod_brew_tag, bug_keys) for bug in bugs: @@ -148,27 +148,11 @@ def _process_shipped_bugs(self, logger, bug_keys, bugs, jira_client: JIRA, nvrs, if current_status.lower() != "closed": new_status = 'CLOSED' message = f"Elliott changed bug status from {current_status} to {new_status} because {nvrs} was/were already shipped and tagged into {prod_brew_tag}." - self._move_jira(jira_client, bug, new_status, message) + early_kernel.move_jira(logger, self.dry_run, jira_client, bug, new_status, message) else: logger.info("No need to move %s because its status is %s", bug.key, current_status) - return f"Build(s) {nvrs} was/were already shipped and tagged into {prod_brew_tag}." # see wording NOTE - def _process_shipped_tracker(self, logger, tracker, jira_client: JIRA, nvrs) -> List[str]: - # when NVRs are shipped, ensure the associated tracker is closed with a comment - # and a link to any advisory that shipped them - logger.info("Build(s) %s shipped (tagged into %s). Looking for advisories...", nvrs) - tracker_messages = [] - - logger.info("Moving tracker Jira %s to CLOSED...") - current_status: str = tracker.fields.status.name - if current_status.lower() != "closed": - self._move_jira(jira_client, tracker, "CLOSED") - else: - logger.info("No need to move %s because its status is %s", tracker.key, current_status) - - return tracker_messages - - def _process_candidate_bugs(self, logger, bug_keys, bugs, jira_client: JIRA, nvrs, candidate_brew_tag) -> str: + def _process_candidate_bugs(self, logger, bug_keys, bugs, jira_client: JIRA, nvrs, candidate_brew_tag): # when NVRs are tagged, ensure the associated bugs are modified with a comment logger.info("Build(s) %s tagged into %s. Moving Jira(s) %s to MODIFIED...", nvrs, candidate_brew_tag, bug_keys) for bug in bugs: @@ -176,10 +160,9 @@ def _process_candidate_bugs(self, logger, bug_keys, bugs, jira_client: JIRA, nvr if current_status.lower() in {"new", "assigned", "post"}: new_status = 'MODIFIED' message = f"Elliott changed bug status from {current_status} to {new_status} because {nvrs} was/were already tagged into {candidate_brew_tag}." - self._move_jira(jira_client, bug, new_status, message) + early_kernel.move_jira(logger, self.dry_run, jira_client, bug, new_status, message) else: logger.info("No need to move %s because its status is %s", bug.key, current_status) - return f"Build(s) {nvrs} was/were already tagged into {candidate_brew_tag}." # see wording NOTE def _update_jira_bugs(self, jira_client: JIRA, found_bugs: List[Issue], koji_api: koji.ClientSession, config: KernelBugSweepConfig): logger = self._runtime.logger @@ -189,42 +172,18 @@ def _update_jira_bugs(self, jira_client: JIRA, found_bugs: List[Issue], koji_api nvrs, candidate, shipped = early_kernel.get_tracker_builds_and_tags(logger, tracker, koji_api, config.target_jira) bugs = tracker_bugs[tracker_id] bug_keys = [bug.key for bug in bugs] - tracker_message = [] if shipped: - tracker_message.append(self._process_shipped_bugs(logger, bug_keys, bugs, jira_client, nvrs, shipped)) - tracker_message.extend(self._process_shipped_tracker(logger, tracker, jira_client, nvrs)) + self._process_shipped_bugs(logger, bug_keys, bugs, jira_client, nvrs, shipped) + if self.update_tracker: + early_kernel.process_shipped_tracker(logger, self.dry_run, jira_client, tracker, nvrs, shipped) elif candidate: - tracker_message.append(self._process_candidate_bugs(logger, bug_keys, bugs, jira_client, nvrs, candidate)) - - if self.comment and tracker_message: - logger.info("Checking if making a comment on tracker %s is needed", tracker.key) - # wording NOTE: this logic will re-comment on past bugs if the wording is not - # exactly as previously commented. think long and hard before changing the wording - # of any of these comments. - comments = jira_client.comments(tracker.key) - for message in tracker_message: - if any(map(lambda comment: comment.body == message, comments)): - logger.info("A comment was already made on %s", tracker.key) - continue - logger.info("Making a comment on tracker %s", tracker.key) - if self.dry_run: - logger.warning("[DRY RUN] Would have left a comment on tracker %s", tracker.key) - else: - jira_client.add_comment(tracker.key, message) - logger.info("Left a comment on tracker %s", tracker.key) - - def _move_jira(self, jira_client: JIRA, issue: Issue, new_status: str, comment: Optional[str]): - logger = self._runtime.logger - current_status: str = issue.fields.status.name - logger.info("Moving %s from %s to %s", issue.key, current_status, new_status) - if self.dry_run: - logger.warning("[DRY RUN] Would have moved Jira %s from %s to %s", issue.key, current_status, new_status) - else: - jira_client.assign_issue(issue.key, jira_client.current_user()) - jira_client.transition_issue(issue.key, new_status) - if comment: - jira_client.add_comment(issue.key, comment) - logger.info("Moved %s from %s to %s", issue.key, current_status, new_status) + self._process_candidate_bugs(logger, bug_keys, bugs, jira_client, nvrs, candidate) + if self.update_tracker: + early_kernel.comment_on_tracker( + logger, self.dry_run, jira_client, tracker, + [f"Build(s) {nvrs} was/were already tagged into {candidate}."] + # do not reword, see NOTE in method + ) @staticmethod def _print_report(report: Dict, out: TextIO): @@ -244,10 +203,10 @@ def _print_report(report: Dict, out: TextIO): is_flag=True, default=False, help="Auto move Jira bugs to MODIFIED or CLOSED") -@click.option("--comment", +@click.option("--update-tracker", is_flag=True, default=False, - help="Make comments on KMAINT trackers") + help="Update KMAINT trackers state, links, and comments") @click.option("--dry-run", is_flag=True, default=False, @@ -255,7 +214,7 @@ def _print_report(report: Dict, out: TextIO): @click.pass_obj def find_bugs_kernel_clones_cli( runtime: Runtime, trackers: Tuple[str, ...], issues: Tuple[str, ...], - move: bool, comment: bool, dry_run: bool): + move: bool, update_tracker: bool, dry_run: bool): """Find cloned kernel bugs in JIRA for weekly kernel release through OCP. Example 1: List all bugs in JIRA @@ -266,9 +225,9 @@ def find_bugs_kernel_clones_cli( \b $ elliott -g openshift-4.14 find-bugs:kernel-clones --move - Example 3: Move bugs and leave a comment on the KMAINT tracker + Example 3: Move bugs and update the KMAINT tracker \b - $ elliott -g openshift-4.14 find-bugs:kernel-clones --move --comment + $ elliott -g openshift-4.14 find-bugs:kernel-clones --move --update-tracker """ runtime.initialize(mode="none") cli = FindBugsKernelClonesCli( @@ -276,7 +235,7 @@ def find_bugs_kernel_clones_cli( trackers=trackers, bugs=issues, move=move, - comment=comment, + update_tracker=update_tracker, dry_run=dry_run ) cli.run() diff --git a/elliottlib/early_kernel.py b/elliottlib/early_kernel.py index 521e9ce3..416aa78a 100644 --- a/elliottlib/early_kernel.py +++ b/elliottlib/early_kernel.py @@ -1,7 +1,9 @@ from typing import Dict, List, Optional, Sequence, TextIO, Tuple, cast import re import koji -from jira import Issue +from errata_tool.build import Build +from errata_tool import Erratum, ErrataException +from jira import Issue, JIRA from elliottlib.config_model import KernelBugSweepConfig from elliottlib import brew @@ -27,3 +29,93 @@ def get_tracker_builds_and_tags( candidate = all(candidate_brew_tag in tags for tags in build_tags) return nvrs, candidate_brew_tag if candidate else None, prod_brew_tag if shipped else None + + +def process_shipped_tracker( + logger, dry_run: bool, + jira_client: JIRA, tracker: Issue, + nvrs: List[str], shipped_tag: str, +) -> List[str]: + # when NVRs are shipped, ensure the associated tracker is closed with a comment + # and a link to any advisory that shipped them + logger.info("Build(s) %s shipped (tagged into %s). Looking for advisories...", nvrs, shipped_tag) + advisories = {} + for nvr in nvrs: + try: + build = Build(nvr) + except ErrataException: + continue # probably build not yet added to an advisory + for errata_id in build.all_errata_ids: + if errata_id in advisories: + continue # already loaded + # TODO: optimize with errata.get_raw_erratum + advisory = Erratum(errata_id=errata_id) + if advisory.errata_state == "SHIPPED_LIVE": + advisories[errata_id] = advisory + if not advisories: + raise RuntimeError(f"NVRs {nvrs} tagged into {shipped_tag} but not found in any shipped advisories!") + + links = set(link.raw['object']['url'] for link in jira_client.remote_links(tracker)) # check if we already linked advisories + tracker_messages = [] + for advisory in advisories.values(): + if advisory.url() in links: + logger.info(f"Tracker {tracker.id} already links {advisory.url()} ({advisory.synopsis})") + continue + tracker_messages.append(f"Build(s) {nvrs} shipped in advisory {advisory.url()} with title:\n{advisory.synopsis}") + if dry_run: + logger.info(f"[DRY RUN] Tracker {tracker.id} would have added link {advisory.url()} ({advisory.errata_name}: {advisory.synopsis})") + else: + jira_client.add_simple_link( + tracker, dict( + url=advisory.url(), + title=f"{advisory.errata_name}: {advisory.synopsis}")) + + logger.info("Moving tracker Jira %s to CLOSED...", tracker) + current_status: str = tracker.fields.status.name + if current_status.lower() != "closed": + if tracker_messages: + comment_on_tracker(logger, dry_run, jira_client, tracker, tracker_messages) + else: + logger.warning("Closing Jira %s without adding any messages; prematurely closed?", tracker) + move_jira(logger, dry_run, jira_client, tracker, "CLOSED") + else: + logger.info("No need to move %s because its status is %s", tracker.key, current_status) + + +def move_jira( + logger, dry_run: bool, + jira_client: JIRA, issue: Issue, + new_status: str, comment: str = None, +): + current_status: str = issue.fields.status.name + if dry_run: + logger.info("[DRY RUN] Would have moved Jira %s from %s to %s", issue.key, current_status, new_status) + else: + logger.info("Moving %s from %s to %s", issue.key, current_status, new_status) + jira_client.assign_issue(issue.key, jira_client.current_user()) + jira_client.transition_issue(issue.key, new_status) + if comment: + jira_client.add_comment(issue.key, comment) + logger.info("Moved %s from %s to %s", issue.key, current_status, new_status) + + +def comment_on_tracker( + logger, dry_run: bool, + jira_client: JIRA, tracker: Issue, + comments: List[str], +): + # wording NOTE: commenting is intended to avoid duplicates, but this logic may re-comment on old + # trackers if the wording changes after previously commented. think long and hard before + # changing the wording of any tracker comments. + logger.info("Checking if making a comment on tracker %s is needed", tracker.key) + previous_comments = jira_client.comments(tracker.key) + for comment in comments: + if any(previous.body == comment for previous in previous_comments): + logger.info("Intended comment was already made on %s", tracker.key) + continue + logger.info("Making a comment on tracker %s", tracker.key) + if dry_run: + logger.info("[DRY RUN] Would have left a comment on tracker %s", tracker.key) + else: + jira_client.add_comment(tracker.key, comment) + logger.info("Left a comment on tracker %s", tracker.key) diff --git a/tests/test_find_bugs_kernel_cli.py b/tests/test_find_bugs_kernel_cli.py index b242caf9..758e9a59 100644 --- a/tests/test_find_bugs_kernel_cli.py +++ b/tests/test_find_bugs_kernel_cli.py @@ -12,6 +12,7 @@ from elliottlib.config_model import KernelBugSweepConfig from elliottlib.runtime import Runtime from elliottlib.bzutil import JIRABugTracker +from elliottlib import early_kernel class TestFindBugsKernelCli(IsolatedAsyncioTestCase): @@ -27,7 +28,7 @@ def test_find_kmaint_trackers(self): def test_get_and_filter_bugs(self): runtime = MagicMock() cli = FindBugsKernelCli( - runtime=runtime, trackers=[], clone=True, reconcile=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], clone=True, reconcile=True, update_tracker=True, dry_run=False) bz_client = MagicMock(spec=Bugzilla) bz_client.getbugs.return_value = [ MagicMock(spec=Bug, id=1, weburl="irrelevant", cf_zstream_target_release=None), @@ -45,7 +46,7 @@ def test_get_and_filter_bugs(self): def test_find_bugs(self, _get_and_filter_bugs: Mock): runtime = MagicMock() cli = FindBugsKernelCli( - runtime=runtime, trackers=[], clone=True, reconcile=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], clone=True, reconcile=True, update_tracker=True, dry_run=False) bz_client = MagicMock(spec=Bugzilla) tracker = MagicMock(spec=Issue, key="TRACKER-1", fields=MagicMock( summary="foo-1.0.1-1.el8_6 and bar-1.0.1-1.el8_6 early delivery via OCP", @@ -72,7 +73,7 @@ def test_clone_bugs1(self): # Test cloning a bug that has not already been cloned runtime = MagicMock() cli = FindBugsKernelCli( - runtime=runtime, trackers=[], clone=True, reconcile=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], clone=True, reconcile=True, update_tracker=True, dry_run=False) jira_client = MagicMock(spec=JIRA) bugs = [ MagicMock(spec=Bug, id=1, weburl="https://example.com/1", @@ -112,7 +113,7 @@ def test_clone_bugs2(self): # Test cloning a bug that has already been cloned runtime = MagicMock() cli = FindBugsKernelCli( - runtime=runtime, trackers=[], clone=True, reconcile=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], clone=True, reconcile=True, update_tracker=True, dry_run=False) jira_client = MagicMock(spec=JIRA) bugs = [ MagicMock(spec=Bug, id=1, weburl="https://example.com/1", @@ -169,11 +170,8 @@ def test_print_report(self): """.strip()) @patch("elliottlib.brew.get_builds_tags") - def test_comment_on_tracker(self, get_builds_tags: Mock): - runtime = MagicMock() - cli = FindBugsKernelCli( - runtime=runtime, trackers=[], clone=True, reconcile=True, comment=True, dry_run=False) - jira_client = MagicMock(spec=JIRA) + def test_get_tracker_builds_and_tags(self, get_builds_tags: Mock): + logger = MagicMock() conf = KernelBugSweepConfig.TargetJiraConfig( project="TARGET-PROJECT", component="Target Component", @@ -188,20 +186,33 @@ def test_comment_on_tracker(self, get_builds_tags: Mock): [{"name": "irrelevant-1"}, {"name": "fake-candidate"}], [{"name": "irrelevant-2"}, {"name": "fake-candidate"}], ] - jira_client.comments.return_value = [] + + nvrs, candidate, shipped = early_kernel.get_tracker_builds_and_tags(logger, tracker, koji_api, conf) + self.assertEqual(["kernel-1.0.1-1.fake", "kernel-rt-1.0.1-1.fake"], nvrs) + self.assertEqual("fake-candidate", candidate) + self.assertFalse(shipped) + + def test_comment_on_tracker(self): + logger = MagicMock() + jira_client = MagicMock(spec=JIRA) + tracker = MagicMock(spec=Issue, key="TRACKER-1", fields=MagicMock( + summary="kernel-1.0.1-1.fake and kernel-rt-1.0.1-1.fake early delivery via OCP", + description="Fixes bugzilla.redhat.com/show_bug.cgi?id=5 and bz6.", + )) + comment1, comment2 = "Comment 1", "Comment 2" # Test 1: making a comment - cli._comment_on_tracker(jira_client, tracker, koji_api, conf) - jira_client.add_comment.assert_called_once_with("TRACKER-1", "Build(s) ['kernel-1.0.1-1.fake', 'kernel-rt-1.0.1-1.fake'] was/were already tagged into fake-candidate.") + jira_client.comments.return_value = [MagicMock(body=comment1)] + early_kernel.comment_on_tracker(logger, False, jira_client, tracker, [comment2]) + jira_client.add_comment.assert_called_once_with("TRACKER-1", comment2) # Test 2: not making a comment because a comment has been made jira_client.comments.return_value = [ - MagicMock(body="irrelevant 1"), - MagicMock(body="Build(s) ['kernel-1.0.1-1.fake', 'kernel-rt-1.0.1-1.fake'] was/were already tagged into fake-candidate."), - MagicMock(body="irrelevant 2"), + MagicMock(body=comment1), + MagicMock(body=comment2), ] jira_client.add_comment.reset_mock() - cli._comment_on_tracker(jira_client, tracker, koji_api, conf) + early_kernel.comment_on_tracker(logger, False, jira_client, tracker, [comment2, comment1]) jira_client.add_comment.assert_not_called() def test_new_jira_fields_from_bug(self): @@ -242,11 +253,11 @@ def test_new_jira_fields_from_bug(self): @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._print_report") @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._clone_bugs") - @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._comment_on_tracker") + @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._update_tracker") @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._find_bugs") @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._find_kmaint_trackers") async def test_run_without_specified_trackers( - self, _find_kmaint_trackers: Mock, _find_bugs: Mock, _comment_on_tracker: Mock, + self, _find_kmaint_trackers: Mock, _find_bugs: Mock, _update_tracker: Mock, _clone_bugs: Mock, _print_report: Mock): runtime = MagicMock( autospec=Runtime, assembly_type=AssemblyTypes.STREAM, @@ -292,18 +303,18 @@ async def test_run_without_specified_trackers( summary="fake summary 10003", description="fake description 10003"), ] cli = FindBugsKernelCli( - runtime=runtime, trackers=[], clone=True, reconcile=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], clone=True, reconcile=True, update_tracker=True, dry_run=False) await cli.run() - _comment_on_tracker.assert_called_once_with(ANY, _find_kmaint_trackers.return_value[0], ANY, ANY) + _update_tracker.assert_called_once_with(ANY, _find_kmaint_trackers.return_value[0], ANY, ANY) _clone_bugs.assert_called_once_with(ANY, _find_bugs.return_value, ANY) @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._print_report") @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._clone_bugs") - @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._comment_on_tracker") + @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._update_tracker") @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._find_bugs") @patch("elliottlib.cli.find_bugs_kernel_cli.FindBugsKernelCli._find_kmaint_trackers") async def test_run_with_specified_trackers( - self, _find_kmaint_trackers: Mock, _find_bugs: Mock, _comment_on_tracker: Mock, + self, _find_kmaint_trackers: Mock, _find_bugs: Mock, _update_tracker: Mock, _clone_bugs: Mock, _print_report: Mock): runtime = MagicMock( autospec=Runtime, assembly_type=AssemblyTypes.STREAM, @@ -349,8 +360,8 @@ async def test_run_with_specified_trackers( summary="fake summary 10003", description="fake description 10003"), ] cli = FindBugsKernelCli( - runtime=runtime, trackers=["TRACKER-999"], clone=True, reconcile=True, comment=True, dry_run=False) + runtime=runtime, trackers=["TRACKER-999"], clone=True, reconcile=True, update_tracker=True, dry_run=False) await cli.run() - _comment_on_tracker.assert_called_once() + _update_tracker.assert_called_once() _clone_bugs.assert_called_once_with(ANY, _find_bugs.return_value, ANY) _find_kmaint_trackers.assert_not_called() diff --git a/tests/test_find_bugs_kernel_clones_cli.py b/tests/test_find_bugs_kernel_clones_cli.py index b6987732..3bc97b2a 100644 --- a/tests/test_find_bugs_kernel_clones_cli.py +++ b/tests/test_find_bugs_kernel_clones_cli.py @@ -9,6 +9,7 @@ from elliottlib.cli.find_bugs_kernel_clones_cli import FindBugsKernelClonesCli from elliottlib.config_model import KernelBugSweepConfig from elliottlib.bzutil import JIRABugTracker +from elliottlib import early_kernel class TestFindBugsKernelClonesCli(IsolatedAsyncioTestCase): @@ -34,7 +35,7 @@ def setUp(self) -> None: def test_get_jira_bugs(self): runtime = MagicMock() cli = FindBugsKernelClonesCli( - runtime=runtime, trackers=[], bugs=[], move=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], bugs=[], move=True, update_tracker=True, dry_run=False) jira_client = MagicMock(spec=JIRA) component = MagicMock() component.configure_mock(name="RHCOS") @@ -64,7 +65,7 @@ def test_search_for_jira_bugs(self): jira_client.search_issues.assert_called_once_with(expected_jql, maxResults=0) self.assertEqual([issue.key for issue in actual], ["FOO-1", "FOO-2", "FOO-3"]) - @patch("elliottlib.cli.find_bugs_kernel_clones_cli.FindBugsKernelClonesCli._move_jira") + @patch("elliottlib.early_kernel.move_jira") @patch("elliottlib.brew.get_builds_tags") def test_update_jira_bugs(self, get_builds_tags: Mock, _move_jira: Mock): runtime = MagicMock() @@ -78,7 +79,7 @@ def test_update_jira_bugs(self, get_builds_tags: Mock, _move_jira: Mock): "fields.description": "Fixes bugzilla.redhat.com/show_bug.cgi?id=5 and bz6.", }) cli = FindBugsKernelClonesCli( - runtime=runtime, trackers=[], bugs=[], move=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], bugs=[], move=True, update_tracker=True, dry_run=False) bugs = [ MagicMock(spec=Issue, **{ "key": "FOO-1", "fields": MagicMock(), @@ -103,14 +104,12 @@ def test_update_jira_bugs(self, get_builds_tags: Mock, _move_jira: Mock): [{"name": "irrelevant-2"}, {"name": "rhaos-4.14-rhel-9-candidate"}], ] cli._update_jira_bugs(jira_client, bugs, koji_api, self._config) - _move_jira.assert_any_call(jira_client, bugs[0], "MODIFIED", ANY) - _move_jira.assert_any_call(jira_client, bugs[1], "MODIFIED", ANY) + _move_jira.assert_any_call(ANY, False, jira_client, bugs[0], "MODIFIED", ANY) + _move_jira.assert_any_call(ANY, False, jira_client, bugs[1], "MODIFIED", ANY) def test_move_jira(self): runtime = MagicMock() jira_client = MagicMock(spec=JIRA) - cli = FindBugsKernelClonesCli( - runtime=runtime, trackers=[], bugs=[], move=True, comment=True, dry_run=False) comment = "Test message" issue = MagicMock(spec=Issue, **{ "key": "FOO-1", "fields": MagicMock(), @@ -118,7 +117,7 @@ def test_move_jira(self): "fields.status.name": "New", }) jira_client.current_user.return_value = "fake-user" - cli._move_jira(jira_client, issue, "MODIFIED", comment) + early_kernel.move_jira(runtime.logger(), False, jira_client, issue, "MODIFIED", comment) jira_client.assign_issue.assert_called_once_with("FOO-1", "fake-user") jira_client.transition_issue.assert_called_once_with("FOO-1", "MODIFIED") @@ -163,7 +162,6 @@ async def test_run_without_specified_bugs(self, _search_for_jira_bugs: Mock, _up }, } ) - jira_client = runtime.bug_trackers.return_value._client found_bugs = [ MagicMock(spec=Issue, **{ "key": "FOO-1", "fields": MagicMock(), @@ -187,9 +185,9 @@ async def test_run_without_specified_bugs(self, _search_for_jira_bugs: Mock, _up ] _search_for_jira_bugs.return_value = found_bugs cli = FindBugsKernelClonesCli( - runtime=runtime, trackers=[], bugs=[], move=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], bugs=[], move=True, update_tracker=True, dry_run=False) cli.run() - _update_jira_bugs.assert_called_once_with(jira_client, found_bugs, ANY, ANY) + _update_jira_bugs.assert_called_once_with(ANY, found_bugs, ANY, ANY) expected_report = { 'jira_issues': [ {'key': 'FOO-1', 'summary': 'Fake bug 1', 'status': 'New'}, @@ -226,7 +224,6 @@ async def test_run_with_specified_bugs(self, _search_for_jira_bugs: Mock, _updat }, } ) - jira_client = runtime.bug_trackers.return_value._client found_bugs = [ MagicMock(spec=Issue, **{ "key": "FOO-1", "fields": MagicMock(), @@ -250,9 +247,10 @@ async def test_run_with_specified_bugs(self, _search_for_jira_bugs: Mock, _updat ] _get_jira_bugs.return_value = found_bugs cli = FindBugsKernelClonesCli( - runtime=runtime, trackers=[], bugs=["FOO-1", "FOO-2", "FOO-3"], move=True, comment=True, dry_run=False) + runtime=runtime, trackers=[], bugs=["FOO-1", "FOO-2", "FOO-3"], move=True, + update_tracker=True, dry_run=False) cli.run() - _update_jira_bugs.assert_called_once_with(jira_client, found_bugs, ANY, ANY) + _update_jira_bugs.assert_called_once_with(ANY, found_bugs, ANY, ANY) expected_report = { 'jira_issues': [ {'key': 'FOO-1', 'summary': 'Fake bug 1', 'status': 'New'},