From f24ee11fb60114c4b10e5235598188a7395a7398 Mon Sep 17 00:00:00 2001 From: Joel Maher Date: Mon, 5 Aug 2024 17:48:55 -0700 Subject: [PATCH] Allow for bugzilla management to use a minimum threshold for 'reopened bugs' (#8144) --- tests/conftest.py | 5 ++++ tests/etl/test_bugzilla.py | 49 ++++++++++++++++++++++++++++---------- treeherder/etl/bugzilla.py | 16 ++++++++----- 3 files changed, 51 insertions(+), 19 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0b485d6e9..d4e7a6440 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -324,6 +324,11 @@ def test_job(eleven_job_blobs, create_jobs): return create_jobs([job])[0] +@pytest.fixture +def test_jobs(eleven_job_blobs_new_date, create_jobs): + return create_jobs(eleven_job_blobs_new_date) + + @pytest.fixture def test_two_jobs_tc_metadata(eleven_job_blobs_new_date, create_jobs): job_1, job_2 = eleven_job_blobs_new_date[0:2] diff --git a/tests/etl/test_bugzilla.py b/tests/etl/test_bugzilla.py index ebecec56e..89f86d3a6 100644 --- a/tests/etl/test_bugzilla.py +++ b/tests/etl/test_bugzilla.py @@ -19,29 +19,50 @@ def test_bz_api_process(mock_bugzilla_api_request): assert Bugscache.objects.count() == 28 -def test_bz_reopen_bugs(request, mock_bugzilla_reopen_request, client, test_job, test_user, bugs): +@pytest.mark.parametrize( + "minimum_failures_to_reopen", + [1, 3], +) +def test_bz_reopen_bugs( + request, + mock_bugzilla_reopen_request, + client, + test_jobs, + test_user, + bugs, + minimum_failures_to_reopen, +): """ Test expected bugs get reopened. """ - bug = bugs[0] client.force_authenticate(user=test_user) + request.config.cache.set("reopened_bugs", {}) incomplete_bugs = [bug for bug in bugs if bug.resolution == "INCOMPLETE"] not_incomplete_bugs = [bug for bug in bugs if bug.resolution != "INCOMPLETE"] + idx = 0 for bug in [ - not_incomplete_bugs[0], - not_incomplete_bugs[2], incomplete_bugs[0], incomplete_bugs[2], + incomplete_bugs[0], + incomplete_bugs[2], + incomplete_bugs[0], + not_incomplete_bugs[0], + not_incomplete_bugs[2], ]: - submit_obj = {"job_id": test_job.id, "bug_id": bug.id, "type": "manual"} + submit_obj = {"job_id": test_jobs[idx].id, "bug_id": bug.id, "type": "manual"} client.post( - reverse("bug-job-map-list", kwargs={"project": test_job.repository.name}), + reverse("bug-job-map-list", kwargs={"project": test_jobs[idx].repository.name}), data=submit_obj, ) + idx += 1 + if idx % 11 == 0: + idx = 0 + process = BzApiBugProcess() + process.minimum_failures_to_reopen = minimum_failures_to_reopen process.run() reopened_bugs = request.config.cache.get("reopened_bugs", None) @@ -53,19 +74,21 @@ def test_bz_reopen_bugs(request, mock_bugzilla_reopen_request, client, test_job, { "status": "REOPENED", "comment": { - "body": "New failure instance: https://treeherder.mozilla.org/logviewer?job_id=1&repo=mozilla-central" + "body": "New failure instance: https://treeherder.mozilla.org/logviewer?job_id=5&repo=mozilla-central" }, "comment_tags": "treeherder", - } + }, ), - "https://thisisnotbugzilla.org/rest/bug/404": json.dumps( + } + + if process.minimum_failures_to_reopen == 1: + expected_reopen_attempts["https://thisisnotbugzilla.org/rest/bug/404"] = json.dumps( { "status": "REOPENED", "comment": { - "body": "New failure instance: https://treeherder.mozilla.org/logviewer?job_id=1&repo=mozilla-central" + "body": "New failure instance: https://treeherder.mozilla.org/logviewer?job_id=4&repo=mozilla-central" }, "comment_tags": "treeherder", - } - ), - } + }, + ) assert reopened_bugs == expected_reopen_attempts diff --git a/treeherder/etl/bugzilla.py b/treeherder/etl/bugzilla.py index a8da3d198..a6a122dee 100644 --- a/treeherder/etl/bugzilla.py +++ b/treeherder/etl/bugzilla.py @@ -4,7 +4,7 @@ import requests import dateutil.parser from datetime import datetime, timedelta from django.conf import settings -from django.db.models import Max +from django.db.models import Count, Max from treeherder.model.models import Bugscache, BugJobMap from treeherder.utils.github import fetch_json @@ -17,7 +17,7 @@ def reopen_request(url, method, headers, json): make_request(url, method=method, headers=headers, json=json) -def reopen_intermittent_bugs(): +def reopen_intermittent_bugs(minimum_failures_to_reopen=1): # Don't reopen bugs from non-production deployments. if settings.BUGFILER_API_KEY is None: return @@ -29,9 +29,11 @@ def reopen_intermittent_bugs(): # https://github.com/mozilla/relman-auto-nag/blob/c7439e247677333c1cd8c435234b3ef3adc49680/auto_nag/scripts/close_intermittents.py#L17 recent_days = 7 recently_used_bugs = set( - BugJobMap.objects.filter(created__gt=datetime.now() - timedelta(recent_days)).values_list( - "bug_id", flat=True - ) + BugJobMap.objects.filter(created__gt=(datetime.now() - timedelta(recent_days))) + .values("bug_id") + .annotate(num_failures=Count("bug_id")) + .filter(num_failures__gte=minimum_failures_to_reopen) + .values_list("bug_id", flat=True) ) bugs_to_reopen = incomplete_bugs & recently_used_bugs @@ -90,6 +92,8 @@ def fetch_intermittent_bugs(additional_params, limit, duplicate_chain_length): class BzApiBugProcess: + minimum_failures_to_reopen = 1 + def run(self): year_ago = datetime.utcnow() - timedelta(days=365) last_change_time_max = ( @@ -302,4 +306,4 @@ class BzApiBugProcess: modified=last_change_time_max ) - reopen_intermittent_bugs() + reopen_intermittent_bugs(self.minimum_failures_to_reopen)