def testUpdateGroup_InvalidRange_PropertiesAreUpdated(self): anomalies = self._AddAnomalies() # Add another anomaly to the same group as the first two anomalies # by setting its bug ID to match that of an existing group. new_anomaly = anomaly.Anomaly(start_revision=3000, end_revision=4000, group=anomalies[0].group) new_anomaly_key = new_anomaly.put() # Change the anomaly revision to invalid range. alert_group.ModifyAlertsAndAssociatedGroups([new_anomaly], start_revision=10, end_revision=20) # After adding this new anomaly, it belongs to the group, and the group # no longer has a minimum revision range. group = anomalies[0].group.get() self.assertEqual(anomalies[0].group, new_anomaly_key.get().group) self.assertIsNone(group.start_revision) self.assertIsNone(group.end_revision) # Remove the new anomaly from the group by marking it invalid. alert_group.ModifyAlertsAndAssociatedGroups([new_anomaly_key.get()], bug_id=-1) # Now, the anomaly group's revision range is valid again. group = anomalies[0].group.get() self.assertEqual(3000, group.start_revision) self.assertEqual(4000, group.end_revision)
def testUpdateAnomalyGroup_BugIDUntriaged_GroupIsNone(self): anomalies = self._AddAnomalies() group2_key = anomalies[2].group # First give that group an actual bug_id alert_group.ModifyAlertsAndAssociatedGroups(anomalies[2:], bug_id=11111) self.assertEqual(11111, group2_key.get().bug_id) # Now un-triage the bug, since it's the onyl one in the group the group's # bug_id should also be None alert_group.ModifyAlertsAndAssociatedGroups(anomalies[2:], bug_id=None) # Both groups should have been deleted self.assertIsNone(group2_key.get().bug_id)
def testUpdateAnomalyGroup_BugIDInvalid_GroupDeleted(self): anomalies = self._AddAnomalies() group1_key = anomalies[0].group group2_key = anomalies[2].group alert_group.ModifyAlertsAndAssociatedGroups(anomalies, bug_id=-1) # Both groups should have been deleted self.assertIsNone(group1_key.get()) self.assertIsNone(group2_key.get())
def testUpdateAnomalyGroup_BugIDValid_GroupUpdated(self): anomalies = self._AddAnomalies() group1_key = anomalies[0].group group2_key = anomalies[2].group alert_group.ModifyAlertsAndAssociatedGroups(anomalies, bug_id=11111) # Both groups should have their bug_id's updated self.assertEqual(11111, group1_key.get().bug_id) self.assertEqual(11111, group2_key.get().bug_id)
def _MapAnomaliesToMergeIntoBug(dest_bug_id, source_bug_id): """Maps anomalies from source bug to destination bug. Args: dest_bug_id: Merge into bug (base bug) number. source_bug_id: The bug to be merged. """ query = anomaly.Anomaly.query(anomaly.Anomaly.bug_id == int(source_bug_id)) anomalies = query.fetch() alert_group.ModifyAlertsAndAssociatedGroups(anomalies, bug_id=int(dest_bug_id))
def testUpdateAnomalyGroup_BugIDUntriaged_GroupRetainsBugID(self): anomalies = self._AddAnomalies() group1_key = anomalies[0].group # Groups aren't assigned a bug_id until after an alert is placed in the # group with a bug_id. group = group1_key.get() group.bug_id = 12345 group.put() alert_group.ModifyAlertsAndAssociatedGroups(anomalies[:1], bug_id=None) self.assertEqual(12345, group1_key.get().bug_id)
def testMarkAnomalyInvalid_AnomalyIsRemovedFromGroup(self): anomalies = self._AddAnomalies() # At first, two anomalies are in the same group. self.assertEqual(anomalies[0].group, anomalies[1].group) # Mark one of the alerts as invalid. self.assertEqual(12345, anomalies[1].bug_id) alert_group.ModifyAlertsAndAssociatedGroups([anomalies[1]], bug_id=-1) # Now, the alert marked as invalid has no group. # Also, the group's revision range has been updated accordingly. self.assertNotEqual(anomalies[0].group, anomalies[1].group) self.assertIsNone(anomalies[1].group) group = anomalies[0].group.get() self.assertEqual(2000, group.start_revision) self.assertEqual(4000, group.end_revision)
def testUpdateAnomalyRevisionRange_UpdatesGroupRevisionRange(self): anomalies = self._AddAnomalies() # Add another anomaly to the same group as the first two anomalies, # but with a non-overlapping revision range. new_anomaly = anomaly.Anomaly(start_revision=3000, end_revision=4000, group=anomalies[0].group) new_anomaly.put() # Associate it with a group; alert_group.ModifyAlertsAndAssociatedGroups # will update the group's revision range here. alert_group.ModifyAlertsAndAssociatedGroups([new_anomaly], start_revision=3010, end_revision=3020) # Now the group's revision range is updated. group = anomalies[0].group.get() self.assertEqual(3010, group.start_revision) self.assertEqual(3020, group.end_revision)
def _AssociateAlertsWithBug(self, bug_id, urlsafe_keys, is_confirmed): """Sets the bug ID for a set of alerts. This is done after the user enters and submits a bug ID. Args: bug_id: Bug ID number, as a string. urlsafe_keys: Comma-separated Alert keys in urlsafe format. is_confirmed: Whether the user has confirmed that they really want to associate the alerts with a bug even if it appears that the revision ranges don't overlap. """ # Validate bug ID. try: bug_id = int(bug_id) except ValueError: self.RenderHtml('bug_result.html', {'error': 'Invalid bug ID "%s".' % str(bug_id)}) return # Get Anomaly entities and related TestMetadata entities. alert_keys = [ndb.Key(urlsafe=k) for k in urlsafe_keys.split(',')] alert_entities = ndb.get_multi(alert_keys) if not is_confirmed: warning_msg = self._VerifyAnomaliesOverlap(alert_entities, bug_id) if warning_msg: self._ShowConfirmDialog('associate_alerts', warning_msg, { 'bug_id': bug_id, 'keys': urlsafe_keys, }) return alert_group.ModifyAlertsAndAssociatedGroups(alert_entities, bug_id=bug_id) self.RenderHtml('bug_result.html', {'bug_id': bug_id})
def _CreateBug(self, summary, description, labels, components, urlsafe_keys): """Creates a bug, associates it with the alerts, sends a HTML response. Args: summary: The new bug summary string. description: The new bug description string. labels: List of label strings for the new bug. components: List of component strings for the new bug. urlsafe_keys: Comma-separated alert keys in urlsafe format. """ # Only project members (@chromium.org accounts) can be owners of bugs. owner = self.request.get('owner') if owner and not owner.endswith('@chromium.org'): self.RenderHtml( 'bug_result.html', {'error': 'Owner email address must end with @chromium.org.'}) return alert_keys = [ndb.Key(urlsafe=k) for k in urlsafe_keys.split(',')] alerts = ndb.get_multi(alert_keys) if not description: description = 'See the link to graphs below.' milestone_label = _MilestoneLabel(alerts) if milestone_label: labels.append(milestone_label) cc = self.request.get('cc') http = oauth2_decorator.DECORATOR.http() user_issue_tracker_service = issue_tracker_service.IssueTrackerService( http) new_bug_response = user_issue_tracker_service.NewBug( summary, description, labels=labels, components=components, owner=owner, cc=cc) if 'error' in new_bug_response: self.RenderHtml('bug_result.html', {'error': new_bug_response['error']}) return bug_id = new_bug_response['bug_id'] bug_data.Bug(id=bug_id).put() alert_group.ModifyAlertsAndAssociatedGroups(alerts, bug_id=bug_id) comment_body = _AdditionalDetails(bug_id, alerts) # Add the bug comment with the service account, so that there are no # permissions issues. dashboard_issue_tracker_service = issue_tracker_service.IssueTrackerService( utils.ServiceAccountHttp()) dashboard_issue_tracker_service.AddBugComment(bug_id, comment_body) template_params = {'bug_id': bug_id} if all(k.kind() == 'Anomaly' for k in alert_keys): logging.info('Kicking bisect for bug ' + str(bug_id)) bisect_result = auto_bisect.StartNewBisectForBug(bug_id) if 'error' in bisect_result: logging.info('Failed to kick bisect for ' + str(bug_id)) template_params['bisect_error'] = bisect_result['error'] else: logging.info('Successfully kicked bisect for ' + str(bug_id)) template_params.update(bisect_result) else: kinds = set() for k in alert_keys: kinds.add(k.kind()) logging.info( 'Didn\'t kick bisect for bug id %s because alerts had kinds %s', bug_id, list(kinds)) self.RenderHtml('bug_result.html', template_params)