From a19c1e6b9e96f8bb0a347ae916541b6b2402fd00 Mon Sep 17 00:00:00 2001 From: Carlo Lemos Date: Mon, 30 Jun 2025 03:08:32 +0000 Subject: [PATCH 1/5] emission and tests --- src/clusterfuzz/_internal/cron/triage.py | 7 +++ .../appengine/handlers/cron/triage_test.py | 61 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/clusterfuzz/_internal/cron/triage.py b/src/clusterfuzz/_internal/cron/triage.py index be433bc9f1..5dbc118aae 100644 --- a/src/clusterfuzz/_internal/cron/triage.py +++ b/src/clusterfuzz/_internal/cron/triage.py @@ -32,6 +32,7 @@ from clusterfuzz._internal.metrics import crash_stats from clusterfuzz._internal.metrics import logs from clusterfuzz._internal.metrics import monitoring_metrics +from clusterfuzz._internal.metrics import events from . import grouper @@ -538,6 +539,12 @@ def _triage_testcase(testcase, excluded_jobs, all_jobs, throttler): _create_filed_bug_metadata(testcase) issue_filer.notify_issue_update(testcase, 'new') + events.emit( + events.IssueFilingEvent( + testcase=testcase, + issue_tracker=issue_tracker.project, + issue_id=str(testcase.bug_information), + issue_created=True)) logs.info('Filed new issue %s for testcase %d.' % (testcase.bug_information, testcase_id)) diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py index 25476979d9..4562558b7d 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py @@ -16,10 +16,12 @@ import datetime import unittest +from unittest import mock from clusterfuzz._internal.cron import triage from clusterfuzz._internal.datastore import data_handler from clusterfuzz._internal.datastore import data_types +from clusterfuzz._internal.metrics import events from clusterfuzz._internal.tests.test_libs import appengine_test_utils from clusterfuzz._internal.tests.test_libs import helpers from clusterfuzz._internal.tests.test_libs import test_utils @@ -561,3 +563,62 @@ def test_default_limit(self): throttler._get_project_bugs_filing_max(testcase.job_type)) self.assertEqual(None, throttler._get_job_bugs_filing_max(testcase.job_type)) + + +@test_utils.with_cloud_emulators('datastore') +class IssueFilingEventEmitTest(unittest.TestCase): + """Tests emission of IssueFilingEvent when filing an issue.""" + + def setUp(self): + helpers.patch(self, [ + 'clusterfuzz._internal.metrics.events.emit', + 'clusterfuzz._internal.metrics.events._get_datetime_now', + 'clusterfuzz._internal.cron.triage._file_issue', + 'clusterfuzz._internal.cron.triage._is_bug_filed', + 'clusterfuzz._internal.cron.triage._is_crash_important', + 'clusterfuzz._internal.issue_management.issue_tracker_utils.get_issue_tracker_for_testcase', + 'clusterfuzz._internal.datastore.data_handler.critical_tasks_completed', + 'clusterfuzz._internal.cron.triage._check_and_update_similar_bug', + 'clusterfuzz._internal.cron.triage._create_filed_bug_metadata', + 'clusterfuzz._internal.cron.triage.issue_filer.notify_issue_update', + 'clusterfuzz._internal.cron.triage._set_testcase_stuck_state', + 'clusterfuzz._internal.cron.triage._emit_untriaged_testcase_age_metric', + 'clusterfuzz._internal.cron.triage._increment_untriaged_testcase_count', + ]) + self.mock._get_datetime_now.return_value = datetime.datetime(2025, 1, 1) + self.mock._is_bug_filed.return_value = False + self.mock._is_crash_important.return_value = True + self.mock.critical_tasks_completed.return_value = True + self.mock._check_and_update_similar_bug.return_value = False + self.mock._create_filed_bug_metadata.return_value = None + self.mock.notify_issue_update.return_value = None + self.mock._set_testcase_stuck_state.return_value = None + self.mock._emit_untriaged_testcase_age_metric.return_value = None + self.mock._increment_untriaged_testcase_count.return_value = None + + self.testcase = test_utils.create_generic_testcase() + self.testcase.set_metadata('ran_grouper', True) + + issue_tracker = mock.MagicMock() + issue_tracker.project = 'buganizer' + self.mock.get_issue_tracker_for_testcase.return_value = issue_tracker + + def file_issue(testcase, issue_tracker_obj, throttler): + testcase.bug_information = '99' + return True + + self.mock._file_issue.side_effect = file_issue + + def test_event_emitted(self): + triage._triage_testcase( + self.testcase, + excluded_jobs=[], + all_jobs=[self.testcase.job_type], + throttler=triage.Throttler()) + + self.mock.emit.assert_called_once_with( + events.IssueFilingEvent( + testcase=self.testcase, + issue_tracker='buganizer', + issue_id='99', + issue_created=True)) From d3180336e455287171ca33495b48145e52e69796 Mon Sep 17 00:00:00 2001 From: Carlo Lemos Date: Mon, 30 Jun 2025 03:20:44 +0000 Subject: [PATCH 2/5] fix lint --- src/clusterfuzz/_internal/cron/triage.py | 2 +- .../_internal/tests/appengine/handlers/cron/triage_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clusterfuzz/_internal/cron/triage.py b/src/clusterfuzz/_internal/cron/triage.py index 5dbc118aae..0c765acebf 100644 --- a/src/clusterfuzz/_internal/cron/triage.py +++ b/src/clusterfuzz/_internal/cron/triage.py @@ -30,9 +30,9 @@ from clusterfuzz._internal.issue_management import issue_tracker_policy from clusterfuzz._internal.issue_management import issue_tracker_utils from clusterfuzz._internal.metrics import crash_stats +from clusterfuzz._internal.metrics import events from clusterfuzz._internal.metrics import logs from clusterfuzz._internal.metrics import monitoring_metrics -from clusterfuzz._internal.metrics import events from . import grouper diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py index 4562558b7d..a31a07e6c5 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py @@ -603,7 +603,7 @@ def setUp(self): issue_tracker.project = 'buganizer' self.mock.get_issue_tracker_for_testcase.return_value = issue_tracker - def file_issue(testcase, issue_tracker_obj, throttler): + def file_issue(testcase, issue_tracker_obj, throttler): # pylint: disable=unused-argument testcase.bug_information = '99' return True From 2db5d16fbee8b75a7222c9b80a1c2cbdda806b22 Mon Sep 17 00:00:00 2001 From: Carlo Lemos Date: Tue, 1 Jul 2025 17:04:04 +0000 Subject: [PATCH 3/5] new event when issue failed --- src/clusterfuzz/_internal/cron/triage.py | 5 +++++ .../appengine/handlers/cron/triage_test.py | 19 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/clusterfuzz/_internal/cron/triage.py b/src/clusterfuzz/_internal/cron/triage.py index 0c765acebf..6f6ba4da0d 100644 --- a/src/clusterfuzz/_internal/cron/triage.py +++ b/src/clusterfuzz/_internal/cron/triage.py @@ -532,6 +532,11 @@ def _triage_testcase(testcase, excluded_jobs, all_jobs, throttler): _emit_untriaged_testcase_age_metric(testcase, PENDING_FILING) _increment_untriaged_testcase_count(testcase.job_type, PENDING_FILING) logs.info(f'Issue filing failed for testcase id {testcase_id}') + events.emit( + events.IssueFilingEvent( + testcase=testcase, + issue_tracker=issue_tracker.project, + issue_created=False)) return _set_testcase_stuck_state(testcase, False) diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py index a31a07e6c5..18e0a17234 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py @@ -603,13 +603,12 @@ def setUp(self): issue_tracker.project = 'buganizer' self.mock.get_issue_tracker_for_testcase.return_value = issue_tracker + def test_event_emitted(self): def file_issue(testcase, issue_tracker_obj, throttler): # pylint: disable=unused-argument testcase.bug_information = '99' return True - self.mock._file_issue.side_effect = file_issue - def test_event_emitted(self): triage._triage_testcase( self.testcase, excluded_jobs=[], @@ -622,3 +621,19 @@ def test_event_emitted(self): issue_tracker='buganizer', issue_id='99', issue_created=True)) + + def test_event_emitted_on_failure(self): + """Tests that the IssueFilingEvent is emitted on a failed filing.""" + self.mock._file_issue.return_value = False + + triage._triage_testcase( + self.testcase, + excluded_jobs=[], + all_jobs=[self.testcase.job_type], + throttler=triage.Throttler()) + + self.mock.emit.assert_called_once_with( + events.IssueFilingEvent( + testcase=self.testcase, + issue_tracker='buganizer', + issue_created=False)) From 91fbdd33b19e46295b6ee6b43d528731921cd82d Mon Sep 17 00:00:00 2001 From: Carlo Lemos Date: Tue, 1 Jul 2025 17:12:52 +0000 Subject: [PATCH 4/5] renaming issue_tracker with issue_tracker_id --- src/clusterfuzz/_internal/cron/triage.py | 4 ++-- src/clusterfuzz/_internal/datastore/data_types.py | 4 ++-- src/clusterfuzz/_internal/metrics/events.py | 4 ++-- .../tests/appengine/handlers/cron/triage_test.py | 8 +++++--- .../_internal/tests/core/metrics/events_test.py | 12 ++++++------ 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/clusterfuzz/_internal/cron/triage.py b/src/clusterfuzz/_internal/cron/triage.py index 6f6ba4da0d..f2dc3790b1 100644 --- a/src/clusterfuzz/_internal/cron/triage.py +++ b/src/clusterfuzz/_internal/cron/triage.py @@ -535,7 +535,7 @@ def _triage_testcase(testcase, excluded_jobs, all_jobs, throttler): events.emit( events.IssueFilingEvent( testcase=testcase, - issue_tracker=issue_tracker.project, + issue_tracker_project=issue_tracker.project, issue_created=False)) return @@ -547,7 +547,7 @@ def _triage_testcase(testcase, excluded_jobs, all_jobs, throttler): events.emit( events.IssueFilingEvent( testcase=testcase, - issue_tracker=issue_tracker.project, + issue_tracker_project=issue_tracker.project, issue_id=str(testcase.bug_information), issue_created=True)) logs.info('Filed new issue %s for testcase %d.' % (testcase.bug_information, diff --git a/src/clusterfuzz/_internal/datastore/data_types.py b/src/clusterfuzz/_internal/datastore/data_types.py index c5c7fae419..2ba2ef484b 100644 --- a/src/clusterfuzz/_internal/datastore/data_types.py +++ b/src/clusterfuzz/_internal/datastore/data_types.py @@ -1665,8 +1665,8 @@ class TestcaseLifecycleEvent(Model): rejection_reason = ndb.StringProperty() ### Issue Filing. - # Name of the issue tracker (e.g., buganizer). - issue_tracker = ndb.StringProperty() + # Name of the project associated with the issue tracker. + issue_tracker_project = ndb.StringProperty() # ID from issue tracker bug (same as `bug_information` for Testcase). issue_id = ndb.StringProperty() diff --git a/src/clusterfuzz/_internal/metrics/events.py b/src/clusterfuzz/_internal/metrics/events.py index 335f2cb51e..6a8d4e2475 100644 --- a/src/clusterfuzz/_internal/metrics/events.py +++ b/src/clusterfuzz/_internal/metrics/events.py @@ -152,8 +152,8 @@ class TestcaseRejectionEvent(BaseTestcaseEvent, BaseTaskEvent): class IssueFilingEvent(BaseTestcaseEvent, BaseTaskEvent): """Issue filing event.""" event_type: str = field(default=EventTypes.ISSUE_FILING, init=False) - # Either buganizer or some_other_board. - issue_tracker: str | None = None + # Name of the project associate with the issue tracker. + issue_tracker_project: str | None = None # The number of the issue on the issue tracker. issue_id: str | None = None # If the issue filing attempt was successful. diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py index 18e0a17234..e1e4e1b966 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py @@ -600,13 +600,15 @@ def setUp(self): self.testcase.set_metadata('ran_grouper', True) issue_tracker = mock.MagicMock() - issue_tracker.project = 'buganizer' + issue_tracker.project = 'oss-fuzz' self.mock.get_issue_tracker_for_testcase.return_value = issue_tracker def test_event_emitted(self): + def file_issue(testcase, issue_tracker_obj, throttler): # pylint: disable=unused-argument testcase.bug_information = '99' return True + self.mock._file_issue.side_effect = file_issue triage._triage_testcase( @@ -618,7 +620,7 @@ def file_issue(testcase, issue_tracker_obj, throttler): # pylint: disable=unuse self.mock.emit.assert_called_once_with( events.IssueFilingEvent( testcase=self.testcase, - issue_tracker='buganizer', + issue_tracker_project='oss-fuzz', issue_id='99', issue_created=True)) @@ -635,5 +637,5 @@ def test_event_emitted_on_failure(self): self.mock.emit.assert_called_once_with( events.IssueFilingEvent( testcase=self.testcase, - issue_tracker='buganizer', + issue_tracker_project='oss-fuzz', issue_created=False)) diff --git a/src/clusterfuzz/_internal/tests/core/metrics/events_test.py b/src/clusterfuzz/_internal/tests/core/metrics/events_test.py index a95dc11f63..bd7f041431 100644 --- a/src/clusterfuzz/_internal/tests/core/metrics/events_test.py +++ b/src/clusterfuzz/_internal/tests/core/metrics/events_test.py @@ -159,13 +159,13 @@ def test_issue_filing_event(self): event_filing = events.IssueFilingEvent( source=source, testcase=testcase, - issue_tracker='buganizer', + issue_tracker_project='oss-fuzz', issue_id='12345', issue_created=True) self._assert_event_common_fields(event_filing, event_type, source) self._assert_testcase_fields(event_filing, testcase) self._assert_task_fields(event_filing) - self.assertEqual(event_filing.issue_tracker, 'buganizer') + self.assertEqual(event_filing.issue_tracker_project, 'oss-fuzz') self.assertEqual(event_filing.issue_id, '12345') self.assertTrue(event_filing.issue_created) @@ -307,7 +307,7 @@ def test_serialize_issue_filing_event(self): event = events.IssueFilingEvent( source='events_test', testcase=testcase, - issue_tracker='buganizer', + issue_tracker_project='oss-fuzz', issue_id='67890', issue_created=False) event_type = event.event_type @@ -325,7 +325,7 @@ def test_serialize_issue_filing_event(self): self._assert_task_fields(event_entity) # IssueFilingEvent specific assertions - self.assertEqual(event_entity.issue_tracker, 'buganizer') + self.assertEqual(event_entity.issue_tracker_project, 'oss-fuzz') self.assertEqual(event_entity.issue_id, '67890') self.assertFalse(event_entity.issue_created) @@ -437,7 +437,7 @@ def test_deserialize_issue_filing_event(self): event_entity.fuzzer = 'fuzzer1' event_entity.job = 'test_job' event_entity.crash_revision = 2 - event_entity.issue_tracker = 'buganizer' + event_entity.issue_tracker_project = 'oss-fuzz' event_entity.issue_id = '13579' event_entity.issue_created = True event_entity.put() @@ -458,7 +458,7 @@ def test_deserialize_issue_filing_event(self): self.assertEqual(event.crash_revision, 2) # IssueFilingEvent specific assertions - self.assertEqual(event.issue_tracker, 'buganizer') + self.assertEqual(event.issue_tracker_project, 'oss-fuzz') self.assertEqual(event.issue_id, '13579') self.assertTrue(event.issue_created) From f91c8ec4fa69be660d6bfc50c33868a2b6907423 Mon Sep 17 00:00:00 2001 From: Carlo Lemos Date: Tue, 1 Jul 2025 18:24:43 +0000 Subject: [PATCH 5/5] fix lint --- .../_internal/tests/appengine/handlers/cron/triage_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py index e1e4e1b966..fc6bd2a516 100644 --- a/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py +++ b/src/clusterfuzz/_internal/tests/appengine/handlers/cron/triage_test.py @@ -604,6 +604,7 @@ def setUp(self): self.mock.get_issue_tracker_for_testcase.return_value = issue_tracker def test_event_emitted(self): + """Tests that the IssueFilingEvent is emitted on a filled testcase.""" def file_issue(testcase, issue_tracker_obj, throttler): # pylint: disable=unused-argument testcase.bug_information = '99'