@@ -39,8 +39,10 @@ def setUp(self):
3939
4040 helpers .patch (self , [
4141 'clusterfuzz._internal.cron.cleanup.get_top_crashes_for_all_projects_and_platforms' ,
42+ 'clusterfuzz._internal.metrics.events.emit' ,
43+ 'clusterfuzz._internal.metrics.events._get_datetime_now' ,
4244 ])
43-
45+ self . mock . _get_datetime_now . return_value = datetime . datetime ( 2025 , 1 , 1 ) # pylint: disable=protected-access
4446 self .mock .get_top_crashes_for_all_projects_and_platforms .return_value = {
4547 'blah' : {},
4648 'project1' : {
@@ -77,6 +79,7 @@ def test_same_crash_different_security(self):
7779 self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
7880 self .assertEqual (self .testcases [index ].group_id , 0 )
7981 self .assertTrue (self .testcases [index ].is_leader )
82+ self .mock .emit .assert_not_called ()
8083
8184 def test_same_crash_same_security (self ):
8285 """Test that crashes with same crash states and same security flags get
@@ -139,6 +142,7 @@ def test_different_crash_same_security(self):
139142 self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
140143 self .assertEqual (self .testcases [index ].group_id , 0 )
141144 self .assertTrue (self .testcases [index ].is_leader )
145+ self .mock .emit .assert_not_called ()
142146
143147 def test_group_of_one (self ):
144148 """Test that a group id with just one testcase gets removed."""
@@ -152,6 +156,15 @@ def test_group_of_one(self):
152156 self .assertEqual (testcase .group_id , 0 )
153157 self .assertTrue (testcase .is_leader )
154158
159+ self .mock .emit .assert_called_once_with (
160+ events .TestcaseGroupingEvent (
161+ testcase_id = testcase .key .id (),
162+ group_id = 0 ,
163+ previous_group_id = 1 ,
164+ similar_testcase_id = None ,
165+ grouping_reason = events .GroupingReason .UNGROUPED ,
166+ group_merge_reason = None ))
167+
155168 def test_same_unique_crash_type_with_same_state (self ):
156169 """Test that the crashes with same unique crash type and same state get
157170 de-duplicated with one of them removed.."""
@@ -196,6 +209,7 @@ def test_same_unique_crash_type_with_different_state(self):
196209 self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
197210 self .assertEqual (self .testcases [index ].group_id , 0 )
198211 self .assertTrue (self .testcases [index ].is_leader )
212+ self .mock .emit .assert_not_called ()
199213
200214 def test_different_unique_crash_type_with_same_state (self ):
201215 """Test that the crashes with different unique crash type but same state
@@ -316,6 +330,7 @@ def test_top_crasher_for_variant_analysis(self):
316330 for testcase in self .testcases :
317331 self .assertEqual (testcase .group_id , 0 )
318332 self .assertTrue (testcase .is_leader )
333+ self .mock .emit .assert_not_called ()
319334
320335 def test_same_job_type_for_variant_analysis (self ):
321336 """Tests that testcases with the same job_type don't get grouped together"""
@@ -404,6 +419,23 @@ def test_similar_variants_for_variant_analysis(self):
404419 self .assertEqual (self .testcases [i ].group_id , 0 )
405420 self .assertTrue (self .testcases [i ].is_leader )
406421
422+ self .mock .emit .assert_any_call (
423+ events .TestcaseGroupingEvent (
424+ testcase_id = self .testcases [0 ].key .id (),
425+ group_id = self .testcases [0 ].group_id ,
426+ previous_group_id = 0 ,
427+ similar_testcase_id = self .testcases [1 ].key .id (),
428+ grouping_reason = events .GroupingReason .IDENTICAL_VARIANT ,
429+ group_merge_reason = None ))
430+ self .mock .emit .assert_any_call (
431+ events .TestcaseGroupingEvent (
432+ testcase_id = self .testcases [1 ].key .id (),
433+ group_id = self .testcases [1 ].group_id ,
434+ previous_group_id = 0 ,
435+ similar_testcase_id = self .testcases [0 ].key .id (),
436+ grouping_reason = events .GroupingReason .IDENTICAL_VARIANT ,
437+ group_merge_reason = None ))
438+
407439 def test_similar_but_anomalous_variants_for_variant_analysis (self ):
408440 """Tests that testcases with similar variants but anomalous do not
409441 get deduplicated. Anomalous variant matches with more than threshold
@@ -476,6 +508,7 @@ def test_similar_but_anomalous_variants_for_variant_analysis(self):
476508 self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
477509 self .assertEqual (self .testcases [index ].group_id , 0 )
478510 self .assertTrue (self .testcases [index ].is_leader )
511+ self .mock .emit .assert_not_called ()
479512
480513 def test_no_reproducible_for_variant_analysis (self ):
481514 """Tests that no-reproducible testcases with similar variants do not
@@ -515,6 +548,7 @@ def test_no_reproducible_for_variant_analysis(self):
515548 self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
516549 self .assertEqual (self .testcases [index ].group_id , 0 )
517550 self .assertTrue (self .testcases [index ].is_leader )
551+ self .mock .emit .assert_not_called ()
518552
519553 def test_ignored_crash_type_for_variant_analysis (self ):
520554 """Tests that testcases of ignored crash type with similar variants
@@ -555,6 +589,143 @@ def test_ignored_crash_type_for_variant_analysis(self):
555589 self .assertEqual (self .testcases [index ].group_id , 0 )
556590 self .assertTrue (self .testcases [index ].is_leader )
557591
592+ def test_grouping_event_new_group (self ):
593+ """Test correct grouping event for a newly formed group."""
594+ self .testcases [0 ].security_flag = True
595+ self .testcases [0 ].crash_state = 'abcdef'
596+ self .testcases [1 ].security_flag = True
597+ self .testcases [1 ].crash_state = 'abcde'
598+
599+ for t in self .testcases :
600+ t .put ()
601+
602+ grouper .group_testcases ()
603+
604+ for index , t in enumerate (self .testcases ):
605+ self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
606+
607+ # Check testcases 0 and 1 are grouped together.
608+ self .assertNotEqual (self .testcases [0 ].group_id , 0 )
609+ self .assertNotEqual (self .testcases [1 ].group_id , 0 )
610+ self .assertEqual (self .testcases [0 ].group_id , self .testcases [1 ].group_id )
611+
612+ self .mock .emit .assert_any_call (
613+ events .TestcaseGroupingEvent (
614+ testcase_id = self .testcases [0 ].key .id (),
615+ group_id = self .testcases [0 ].group_id ,
616+ previous_group_id = 0 ,
617+ similar_testcase_id = self .testcases [1 ].key .id (),
618+ grouping_reason = events .GroupingReason .SIMILAR_CRASH ,
619+ group_merge_reason = None ))
620+ self .mock .emit .assert_any_call (
621+ events .TestcaseGroupingEvent (
622+ testcase_id = self .testcases [1 ].key .id (),
623+ group_id = self .testcases [1 ].group_id ,
624+ previous_group_id = 0 ,
625+ similar_testcase_id = self .testcases [0 ].key .id (),
626+ grouping_reason = events .GroupingReason .SIMILAR_CRASH ,
627+ group_merge_reason = None ))
628+
629+ def test_grouping_event_assign_one_testcase (self ):
630+ """Test correct grouping event for new testcase assigned to a group."""
631+ self .testcases [0 ].security_flag = True
632+ self .testcases [0 ].crash_state = 'abcdef'
633+ self .testcases [0 ].group_id = 1
634+ self .testcases [1 ].security_flag = True
635+ self .testcases [1 ].crash_state = 'abcde'
636+ self .testcases [1 ].group_id = 0
637+
638+ for t in self .testcases :
639+ t .put ()
640+
641+ grouper .group_testcases ()
642+
643+ for index , t in enumerate (self .testcases ):
644+ self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
645+
646+ # Check testcases 0 and 1 are grouped together.
647+ self .assertEqual (self .testcases [0 ].group_id , 1 )
648+ self .assertEqual (self .testcases [1 ].group_id , 1 )
649+
650+ self .mock .emit .assert_called_once_with (
651+ events .TestcaseGroupingEvent (
652+ testcase_id = self .testcases [1 ].key .id (),
653+ group_id = 1 ,
654+ previous_group_id = 0 ,
655+ similar_testcase_id = self .testcases [0 ].key .id (),
656+ grouping_reason = events .GroupingReason .SIMILAR_CRASH ,
657+ group_merge_reason = None ))
658+
659+ def test_grouping_event_merge_groups (self ):
660+ """Test correct grouping event for merging groups."""
661+ self .testcases [0 ].security_flag = True
662+ self .testcases [0 ].crash_state = 'abcdef'
663+ self .testcases [0 ].group_id = 1
664+ self .testcases [1 ].security_flag = True
665+ self .testcases [1 ].crash_state = 'ghijk'
666+ self .testcases [1 ].group_id = 1
667+
668+ self .testcases .append (test_utils .create_generic_testcase ())
669+ self .testcases [2 ].security_flag = True
670+ self .testcases [2 ].crash_state = 'abcde'
671+ self .testcases [2 ].group_id = 2
672+ self .testcases .append (test_utils .create_generic_testcase ())
673+ self .testcases [3 ].security_flag = True
674+ self .testcases [3 ].crash_state = 'lmnopq'
675+ self .testcases [3 ].group_id = 2
676+
677+ for t in self .testcases :
678+ t .put ()
679+
680+ grouper .group_testcases ()
681+
682+ for index , t in enumerate (self .testcases ):
683+ self .testcases [index ] = data_handler .get_testcase_by_id (t .key .id ())
684+
685+ common_group_id = self .testcases [0 ].group_id
686+ self .assertIn (common_group_id , [1 , 2 ])
687+ # Check testcases are all grouped together.
688+ self .assertEqual (self .testcases [0 ].group_id , common_group_id )
689+ self .assertEqual (self .testcases [1 ].group_id , common_group_id )
690+ self .assertEqual (self .testcases [2 ].group_id , common_group_id )
691+ self .assertEqual (self .testcases [3 ].group_id , common_group_id )
692+
693+ # Avoid possibly flaky test by ensuring which group was kept during merge.
694+ if common_group_id == 1 :
695+ self .mock .emit .assert_any_call (
696+ events .TestcaseGroupingEvent (
697+ testcase_id = self .testcases [2 ].key .id (),
698+ group_id = 1 ,
699+ previous_group_id = 2 ,
700+ similar_testcase_id = self .testcases [0 ].key .id (),
701+ grouping_reason = events .GroupingReason .SIMILAR_CRASH ,
702+ group_merge_reason = None ))
703+ self .mock .emit .assert_any_call (
704+ events .TestcaseGroupingEvent (
705+ testcase_id = self .testcases [3 ].key .id (),
706+ group_id = 1 ,
707+ previous_group_id = 2 ,
708+ similar_testcase_id = self .testcases [2 ].key .id (),
709+ grouping_reason = events .GroupingReason .GROUP_MERGE ,
710+ group_merge_reason = events .GroupingReason .SIMILAR_CRASH ))
711+ else :
712+ self .mock .emit .assert_any_call (
713+ events .TestcaseGroupingEvent (
714+ testcase_id = self .testcases [0 ].key .id (),
715+ group_id = 2 ,
716+ previous_group_id = 1 ,
717+ similar_testcase_id = self .testcases [2 ].key .id (),
718+ grouping_reason = events .GroupingReason .SIMILAR_CRASH ,
719+ group_merge_reason = None ))
720+ self .mock .emit .assert_any_call (
721+ events .TestcaseGroupingEvent (
722+ testcase_id = self .testcases [1 ].key .id (),
723+ group_id = 2 ,
724+ previous_group_id = 1 ,
725+ similar_testcase_id = self .testcases [0 ].key .id (),
726+ grouping_reason = events .GroupingReason .GROUP_MERGE ,
727+ group_merge_reason = events .GroupingReason .SIMILAR_CRASH ))
728+
558729
559730@test_utils .with_cloud_emulators ('datastore' )
560731class GroupExceedMaxTestcasesTest (unittest .TestCase ):
@@ -644,8 +815,12 @@ def test_group_overflow_rejection_events(self):
644815
645816 grouper .group_testcases ()
646817
647- self .assertEqual (5 , len (self .emitted_events ))
818+ # Expected: 30 grouping events + 5 rejection events
819+ self .assertEqual (35 , len (self .emitted_events ))
820+ count_rejection_events = 0
648821 for event in self .emitted_events :
649- self .assertEqual (event .rejection_reason ,
650- events .RejectionReason .GROUPER_OVERFLOW )
651- self .assertEqual (5 , self .mock .emit .call_count )
822+ if event .event_type == events .EventTypes .TESTCASE_REJECTION :
823+ self .assertEqual (event .rejection_reason ,
824+ events .RejectionReason .GROUPER_OVERFLOW )
825+ count_rejection_events += 1
826+ self .assertEqual (5 , count_rejection_events )
0 commit comments