1# Copyright 2020 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4# pylint: disable=too-many-lines
5
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import datetime
11import json
12import uuid
13
14from google.appengine.ext import ndb
15
16from dashboard.common import namespaced_stored_object
17from dashboard.common import testing_common
18from dashboard.common import utils
19from dashboard.models import alert_group
20from dashboard.models import alert_group_workflow
21from dashboard.models import anomaly
22from dashboard.models import subscription
23
24_SERVICE_ACCOUNT_EMAIL = 'service-account@chromium.org'
25
26
27class AlertGroupWorkflowTest(testing_common.TestCase):
28
29  def setUp(self):
30    super(AlertGroupWorkflowTest, self).setUp()
31    self.maxDiff = None
32    self._issue_tracker = testing_common.FakeIssueTrackerService()
33    self._sheriff_config = testing_common.FakeSheriffConfigClient()
34    self._pinpoint = testing_common.FakePinpoint()
35    self._crrev = testing_common.FakeCrrev()
36    self._gitiles = testing_common.FakeGitiles()
37    self._revision_info = testing_common.FakeRevisionInfoClient(
38        infos={
39            'r_chromium_commit_pos': {
40                'name':
41                    'Chromium Commit Position',
42                'url':
43                    'http://test-results.appspot.com/revision_range?start={{R1}}&end={{R2}}',
44            },
45        },
46        revisions={
47            'master/bot/test_suite/measurement/test_case': {
48                0: {
49                    'r_chromium_commit_pos': '0'
50                },
51                100: {
52                    'r_chromium_commit_pos': '100'
53                },
54            }
55        })
56    self._service_account = lambda: _SERVICE_ACCOUNT_EMAIL
57
58  @staticmethod
59  def _AddAnomaly(**kwargs):
60    default = {
61        'test': 'master/bot/test_suite/measurement/test_case',
62        'start_revision': 1,
63        'end_revision': 100,
64        'is_improvement': False,
65        'median_before_anomaly': 1.1,
66        'median_after_anomaly': 1.3,
67        'ownership': {
68            'component': 'Foo>Bar',
69            'emails': ['x@google.com', 'y@google.com'],
70            'info_blurb': 'This is an info blurb.',
71        },
72    }
73    default.update(kwargs)
74
75    tests = default['test'].split('/')
76
77    def GenerateTestDict(tests):
78      if not tests:
79        return {}
80      return {tests[0]: GenerateTestDict(tests[1:])}
81
82    testing_common.AddTests([tests[0]], [tests[1]], GenerateTestDict(tests[2:]))
83    default['test'] = utils.TestKey(default['test'])
84
85    return anomaly.Anomaly(**default).put()
86
87  @staticmethod
88  def _AddAlertGroup(anomaly_key,
89                     subscription_name=None,
90                     issue=None,
91                     anomalies=None,
92                     status=None,
93                     project_id=None,
94                     bisection_ids=None):
95    anomaly_entity = anomaly_key.get()
96    group = alert_group.AlertGroup(
97        id=str(uuid.uuid4()),
98        name=anomaly_entity.benchmark_name,
99        subscription_name=subscription_name or 'sheriff',
100        status=alert_group.AlertGroup.Status.untriaged,
101        project_id=project_id or 'chromium',
102        active=True,
103        revision=alert_group.RevisionRange(
104            repository='chromium',
105            start=anomaly_entity.start_revision,
106            end=anomaly_entity.end_revision,
107        ),
108        bisection_ids=bisection_ids or [],
109    )
110    if issue:
111      group.bug = alert_group.BugInfo(
112          bug_id=issue.get('id'),
113          project=issue.get('projectId', 'chromium'),
114      )
115      group.project_id = issue.get('projectId', 'chromium')
116    if anomalies:
117      group.anomalies = anomalies
118    if status:
119      group.status = status
120    return group.put()
121
122  def testAddAnomalies_GroupUntriaged(self):
123    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
124    added = [self._AddAnomaly(), self._AddAnomaly()]
125    group = self._AddAlertGroup(anomalies[0], anomalies=anomalies)
126    self._sheriff_config.patterns = {
127        '*': [subscription.Subscription(name='sheriff')],
128    }
129    w = alert_group_workflow.AlertGroupWorkflow(
130        group.get(),
131        sheriff_config=self._sheriff_config,
132        issue_tracker=self._issue_tracker,
133    )
134    w.Process(
135        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
136            now=datetime.datetime.utcnow(),
137            anomalies=ndb.get_multi(anomalies + added),
138            issue={},
139        ))
140
141    self.assertEqual(len(group.get().anomalies), 4)
142    for a in added:
143      self.assertIn(a, group.get().anomalies)
144
145  def testAddAnomalies_GroupTriaged_IssueOpen(self):
146    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
147    added = [self._AddAnomaly(), self._AddAnomaly()]
148    group = self._AddAlertGroup(
149        anomalies[0],
150        issue=self._issue_tracker.issue,
151        anomalies=anomalies,
152        status=alert_group.AlertGroup.Status.triaged,
153    )
154    self._issue_tracker.issue.update({
155        'state': 'open',
156    })
157    self._sheriff_config.patterns = {
158        '*': [
159            subscription.Subscription(name='sheriff', auto_triage_enable=True)
160        ],
161    }
162    w = alert_group_workflow.AlertGroupWorkflow(
163        group.get(),
164        sheriff_config=self._sheriff_config,
165        issue_tracker=self._issue_tracker,
166    )
167    w.Process(
168        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
169            now=datetime.datetime.utcnow(),
170            anomalies=ndb.get_multi(anomalies + added),
171            issue=self._issue_tracker.issue,
172        ))
173
174    self.assertEqual(len(group.get().anomalies), 4)
175    self.assertEqual(group.get().status, alert_group.AlertGroup.Status.triaged)
176    for a in added:
177      self.assertIn(a, group.get().anomalies)
178      self.assertEqual(group.get().bug.bug_id,
179                       self._issue_tracker.add_comment_args[0])
180      self.assertIn('Added 2 regressions to the group',
181                    self._issue_tracker.add_comment_args[1])
182      self.assertIn('4 regressions in test_suite',
183                    self._issue_tracker.add_comment_kwargs['summary'])
184      self.assertIn('sheriff',
185                    self._issue_tracker.add_comment_kwargs['summary'])
186      self.assertFalse(self._issue_tracker.add_comment_kwargs['send_email'])
187
188  def testAddAnomalies_GroupTriaged_IssueClosed(self):
189    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
190    added = [self._AddAnomaly(), self._AddAnomaly()]
191    group = self._AddAlertGroup(
192        anomalies[0],
193        issue=self._issue_tracker.issue,
194        anomalies=anomalies,
195        status=alert_group.AlertGroup.Status.closed,
196    )
197    self._issue_tracker.issue.update({
198        'state':
199            'closed',
200        'comments': [{
201            'id': 1,
202            'author': _SERVICE_ACCOUNT_EMAIL,
203            'updates': {
204                'status': 'WontFix'
205            },
206        }],
207    })
208    self._sheriff_config.patterns = {
209        '*': [
210            subscription.Subscription(name='sheriff', auto_triage_enable=True)
211        ],
212    }
213    w = alert_group_workflow.AlertGroupWorkflow(
214        group.get(),
215        sheriff_config=self._sheriff_config,
216        issue_tracker=self._issue_tracker,
217        service_account=self._service_account,
218    )
219    w.Process(
220        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
221            now=datetime.datetime.utcnow(),
222            anomalies=ndb.get_multi(anomalies + added),
223            issue=self._issue_tracker.issue,
224        ))
225
226    self.assertEqual(len(group.get().anomalies), 4)
227    self.assertEqual('closed', self._issue_tracker.issue.get('state'))
228    for a in added:
229      self.assertIn(a, group.get().anomalies)
230      self.assertEqual(group.get().bug.bug_id,
231                       self._issue_tracker.add_comment_args[0])
232      self.assertIn('Added 2 regressions to the group',
233                    self._issue_tracker.add_comment_args[1])
234      self.assertIn('4 regressions in test_suite',
235                    self._issue_tracker.add_comment_kwargs['summary'])
236      self.assertIn('sheriff',
237                    self._issue_tracker.add_comment_kwargs['summary'])
238      self.assertFalse(self._issue_tracker.add_comment_kwargs['send_email'])
239
240  def testAddAnomalies_GroupTriaged_IssueClosed_AutoBisect(self):
241    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
242    added = [self._AddAnomaly(), self._AddAnomaly()]
243    group = self._AddAlertGroup(
244        anomalies[0],
245        issue=self._issue_tracker.issue,
246        anomalies=anomalies,
247        status=alert_group.AlertGroup.Status.closed,
248    )
249    self._issue_tracker.issue.update({
250        'state':
251            'closed',
252        'comments': [{
253            'id': 1,
254            'author': _SERVICE_ACCOUNT_EMAIL,
255            'updates': {
256                'status': 'WontFix'
257            },
258        }],
259    })
260    self._sheriff_config.patterns = {
261        '*': [
262            subscription.Subscription(
263                name='sheriff',
264                auto_triage_enable=True,
265                auto_bisect_enable=True)
266        ],
267    }
268    w = alert_group_workflow.AlertGroupWorkflow(
269        group.get(),
270        sheriff_config=self._sheriff_config,
271        issue_tracker=self._issue_tracker,
272        service_account=self._service_account,
273    )
274    w.Process(
275        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
276            now=datetime.datetime.utcnow(),
277            anomalies=ndb.get_multi(anomalies + added),
278            issue=self._issue_tracker.issue,
279        ))
280
281    self.assertEqual(len(group.get().anomalies), 4)
282    self.assertEqual('open', self._issue_tracker.issue.get('state'))
283    for a in added:
284      self.assertIn(a, group.get().anomalies)
285      self.assertEqual(group.get().bug.bug_id,
286                       self._issue_tracker.add_comment_args[0])
287      self.assertIn('Added 2 regressions to the group',
288                    self._issue_tracker.add_comment_args[1])
289    self.assertFalse(self._issue_tracker.add_comment_kwargs['send_email'])
290
291  def testUpdate_GroupTriaged_IssueClosed(self):
292    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
293    group = self._AddAlertGroup(
294        anomalies[0],
295        issue=self._issue_tracker.issue,
296        status=alert_group.AlertGroup.Status.triaged,
297    )
298    self._issue_tracker.issue.update({
299        'state':
300            'closed',
301        'comments': [{
302            'id': 1,
303            'author': _SERVICE_ACCOUNT_EMAIL,
304            'updates': {
305                'status': 'WontFix'
306            },
307        }],
308    })
309    self._sheriff_config.patterns = {
310        '*': [
311            subscription.Subscription(name='sheriff', auto_triage_enable=True)
312        ],
313    }
314    w = alert_group_workflow.AlertGroupWorkflow(
315        group.get(),
316        sheriff_config=self._sheriff_config,
317        issue_tracker=self._issue_tracker,
318        service_account=self._service_account,
319    )
320    w.Process(
321        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
322            now=datetime.datetime.utcnow(),
323            anomalies=ndb.get_multi(anomalies),
324            issue=self._issue_tracker.issue,
325        ))
326    self.assertEqual(group.get().status, alert_group.AlertGroup.Status.closed)
327
328  def testAddAnomalies_GroupTriaged_IssueClosed_Manual(self):
329    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
330    added = [self._AddAnomaly(), self._AddAnomaly()]
331    group = self._AddAlertGroup(
332        anomalies[0],
333        issue=self._issue_tracker.issue,
334        anomalies=anomalies,
335        status=alert_group.AlertGroup.Status.closed,
336    )
337    self._issue_tracker.issue.update({
338        'state':
339            'closed',
340        'comments': [{
341            'id': 2,
342            'author': "sheriff@chromium.org",
343            'updates': {
344                'status': 'WontFix'
345            },
346        }, {
347            'id': 1,
348            'author': _SERVICE_ACCOUNT_EMAIL,
349            'updates': {
350                'status': 'WontFix'
351            },
352        }],
353    })
354    self._sheriff_config.patterns = {
355        '*': [
356            subscription.Subscription(
357                name='sheriff',
358                auto_triage_enable=True,
359                auto_bisect_enable=True)
360        ],
361    }
362    w = alert_group_workflow.AlertGroupWorkflow(
363        group.get(),
364        sheriff_config=self._sheriff_config,
365        issue_tracker=self._issue_tracker,
366        service_account=self._service_account,
367    )
368    w.Process(
369        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
370            now=datetime.datetime.utcnow(),
371            anomalies=ndb.get_multi(anomalies + added),
372            issue=self._issue_tracker.issue,
373        ))
374
375    self.assertEqual(len(group.get().anomalies), 4)
376    self.assertEqual('closed', self._issue_tracker.issue.get('state'))
377    for a in added:
378      self.assertIn(a, group.get().anomalies)
379      self.assertEqual(group.get().bug.bug_id,
380                       self._issue_tracker.add_comment_args[0])
381      self.assertIn('Added 2 regressions to the group',
382                    self._issue_tracker.add_comment_args[1])
383    self.assertFalse(self._issue_tracker.add_comment_kwargs['send_email'])
384
385  def testUpdate_GroupTriaged_IssueClosed_AllTriaged(self):
386    anomalies = [
387        self._AddAnomaly(recovered=True),
388        self._AddAnomaly(recovered=True)
389    ]
390    group = self._AddAlertGroup(
391        anomalies[0],
392        issue=self._issue_tracker.issue,
393        anomalies=anomalies,
394        status=alert_group.AlertGroup.Status.triaged,
395    )
396    self._issue_tracker.issue.update({
397        'state':
398            'closed',
399        'comments': [{
400            'id': 1,
401            'author': _SERVICE_ACCOUNT_EMAIL,
402            'updates': {
403                'status': 'WontFix'
404            },
405        }],
406    })
407    self._sheriff_config.patterns = {
408        '*': [
409            subscription.Subscription(name='sheriff', auto_triage_enable=True)
410        ],
411    }
412    w = alert_group_workflow.AlertGroupWorkflow(
413        group.get(),
414        sheriff_config=self._sheriff_config,
415        issue_tracker=self._issue_tracker,
416        service_account=self._service_account,
417    )
418    w.Process(
419        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
420            now=datetime.datetime.utcnow(),
421            anomalies=ndb.get_multi(anomalies),
422            issue=self._issue_tracker.issue,
423        ))
424    self.assertEqual(group.get().status, alert_group.AlertGroup.Status.closed)
425    self.assertIsNone(self._issue_tracker.add_comment_args)
426
427  def testAddAnomalies_GroupTriaged_CommentsNone(self):
428    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
429    added = [self._AddAnomaly(), self._AddAnomaly()]
430    group = self._AddAlertGroup(
431        anomalies[0],
432        issue=self._issue_tracker.issue,
433        anomalies=anomalies,
434        status=alert_group.AlertGroup.Status.closed,
435    )
436    self._issue_tracker.issue.update({
437        'state': 'closed',
438        'comments': None,
439    })
440    self._sheriff_config.patterns = {
441        '*': [
442            subscription.Subscription(
443                name='sheriff',
444                auto_triage_enable=True,
445                auto_bisect_enable=True)
446        ],
447    }
448    w = alert_group_workflow.AlertGroupWorkflow(
449        group.get(),
450        sheriff_config=self._sheriff_config,
451        issue_tracker=self._issue_tracker,
452        service_account=self._service_account,
453    )
454    w.Process(
455        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
456            now=datetime.datetime.utcnow(),
457            anomalies=ndb.get_multi(anomalies + added),
458            issue=self._issue_tracker.issue,
459        ))
460
461    self.assertEqual(len(group.get().anomalies), 4)
462    self.assertEqual('closed', self._issue_tracker.issue.get('state'))
463    for a in added:
464      self.assertIn(a, group.get().anomalies)
465      self.assertEqual(group.get().bug.bug_id,
466                       self._issue_tracker.add_comment_args[0])
467      self.assertIn('Added 2 regressions to the group',
468                    self._issue_tracker.add_comment_args[1])
469    self.assertFalse(self._issue_tracker.add_comment_kwargs['send_email'])
470
471  def testUpdate_GroupClosed_IssueOpen(self):
472    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
473    group = self._AddAlertGroup(
474        anomalies[0],
475        issue=self._issue_tracker.issue,
476        status=alert_group.AlertGroup.Status.closed,
477    )
478    self._issue_tracker.issue.update({
479        'state': 'open',
480    })
481    self._sheriff_config.patterns = {
482        '*': [
483            subscription.Subscription(name='sheriff', auto_triage_enable=True)
484        ],
485    }
486    w = alert_group_workflow.AlertGroupWorkflow(
487        group.get(),
488        sheriff_config=self._sheriff_config,
489        issue_tracker=self._issue_tracker,
490    )
491    w.Process(
492        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
493            now=datetime.datetime.utcnow(),
494            anomalies=ndb.get_multi(anomalies),
495            issue=self._issue_tracker.issue,
496        ))
497
498    self.assertEqual(group.get().status, alert_group.AlertGroup.Status.triaged)
499
500  def testUpdate_GroupTriaged_AlertsAllRecovered(self):
501    anomalies = [
502        self._AddAnomaly(recovered=True),
503        self._AddAnomaly(recovered=True),
504    ]
505    group = self._AddAlertGroup(
506        anomalies[0],
507        issue=self._issue_tracker.issue,
508        status=alert_group.AlertGroup.Status.triaged,
509    )
510    self._issue_tracker.issue.update({
511        'state': 'open',
512    })
513    self._sheriff_config.patterns = {
514        '*': [
515            subscription.Subscription(name='sheriff', auto_triage_enable=True)
516        ],
517    }
518    w = alert_group_workflow.AlertGroupWorkflow(
519        group.get(),
520        sheriff_config=self._sheriff_config,
521        issue_tracker=self._issue_tracker,
522    )
523    w.Process(
524        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
525            now=datetime.datetime.utcnow(),
526            anomalies=ndb.get_multi(anomalies),
527            issue=self._issue_tracker.issue,
528        ))
529
530    self.assertEqual('closed', self._issue_tracker.issue.get('state'))
531
532  def testUpdate_GroupTriaged_AlertsPartRecovered(self):
533    anomalies = [self._AddAnomaly(recovered=True), self._AddAnomaly()]
534    group = self._AddAlertGroup(
535        anomalies[0],
536        issue=self._issue_tracker.issue,
537        status=alert_group.AlertGroup.Status.triaged,
538    )
539    self._issue_tracker.issue.update({
540        'state': 'open',
541    })
542    self._sheriff_config.patterns = {
543        '*': [
544            subscription.Subscription(name='sheriff', auto_triage_enable=True)
545        ],
546    }
547    w = alert_group_workflow.AlertGroupWorkflow(
548        group.get(),
549        sheriff_config=self._sheriff_config,
550        issue_tracker=self._issue_tracker,
551    )
552    w.Process(
553        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
554            now=datetime.datetime.utcnow(),
555            anomalies=ndb.get_multi(anomalies),
556            issue=self._issue_tracker.issue,
557        ))
558
559    self.assertEqual('open', self._issue_tracker.issue.get('state'))
560
561  def testTriage_GroupUntriaged(self):
562    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
563    group = self._AddAlertGroup(
564        anomalies[0],
565        status=alert_group.AlertGroup.Status.untriaged,
566    )
567    self._sheriff_config.patterns = {
568        '*': [
569            subscription.Subscription(name='sheriff', auto_triage_enable=True)
570        ],
571    }
572    w = alert_group_workflow.AlertGroupWorkflow(
573        group.get(),
574        sheriff_config=self._sheriff_config,
575        issue_tracker=self._issue_tracker,
576        revision_info=self._revision_info,
577        config=alert_group_workflow.AlertGroupWorkflow.Config(
578            active_window=datetime.timedelta(days=7),
579            triage_delay=datetime.timedelta(hours=0),
580        ),
581    )
582    w.Process(
583        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
584            now=datetime.datetime.utcnow(),
585            anomalies=ndb.get_multi(anomalies),
586            issue=None,
587        ))
588    self.assertIn('2 regressions', self._issue_tracker.new_bug_args[0])
589    self.assertIn(
590        'Chromium Commit Position: http://test-results.appspot.com/revision_range?start=0&end=100',
591        self._issue_tracker.new_bug_args[1])
592
593  def testTriage_GroupUntriaged_MultiSubscriptions(self):
594    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
595    group = self._AddAlertGroup(
596        anomalies[0],
597        status=alert_group.AlertGroup.Status.untriaged,
598    )
599    self._sheriff_config.patterns = {
600        '*': [
601            subscription.Subscription(name='sheriff'),
602            subscription.Subscription(
603                name='sheriff_not_bind', auto_triage_enable=True)
604        ],
605    }
606    w = alert_group_workflow.AlertGroupWorkflow(
607        group.get(),
608        sheriff_config=self._sheriff_config,
609        issue_tracker=self._issue_tracker,
610        revision_info=self._revision_info,
611        config=alert_group_workflow.AlertGroupWorkflow.Config(
612            active_window=datetime.timedelta(days=7),
613            triage_delay=datetime.timedelta(hours=0),
614        ),
615    )
616    w.Process(
617        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
618            now=datetime.datetime.utcnow(),
619            anomalies=ndb.get_multi(anomalies),
620            issue=None,
621        ))
622    self.assertIsNone(self._issue_tracker.new_bug_args)
623
624  def testTriage_GroupUntriaged_NonChromiumProject(self):
625    anomalies = [self._AddAnomaly()]
626    # TODO(dberris): Figure out a way to not have to hack the fake service to
627    # seed it with the correct issue in the correct project.
628    self._issue_tracker.issues[(
629        'v8', self._issue_tracker.bug_id)] = self._issue_tracker.issues[(
630            'chromium', self._issue_tracker.bug_id)]
631    del self._issue_tracker.issues[('chromium', self._issue_tracker.bug_id)]
632    self._issue_tracker.issues[('v8', self._issue_tracker.bug_id)].update({
633        'projectId': 'v8',
634    })
635    group = self._AddAlertGroup(
636        anomalies[0],
637        status=alert_group.AlertGroup.Status.untriaged,
638        project_id='v8')
639    self._sheriff_config.patterns = {
640        '*': [
641            subscription.Subscription(
642                name='sheriff',
643                auto_triage_enable=True,
644                monorail_project_id='v8')
645        ],
646    }
647    self.assertEqual(group.get().project_id, 'v8')
648    w = alert_group_workflow.AlertGroupWorkflow(
649        group.get(),
650        sheriff_config=self._sheriff_config,
651        issue_tracker=self._issue_tracker,
652        revision_info=self._revision_info,
653        config=alert_group_workflow.AlertGroupWorkflow.Config(
654            active_window=datetime.timedelta(days=7),
655            triage_delay=datetime.timedelta(hours=0),
656        ))
657    w.Process(
658        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
659            now=datetime.datetime.utcnow(),
660            anomalies=ndb.get_multi(anomalies),
661            issue=None))
662    self.assertEqual(group.get().bug.project, 'v8')
663    self.assertEqual(anomalies[0].get().project_id, 'v8')
664
665  def testTriage_GroupUntriaged_MultipleRange(self):
666    anomalies = [
667        self._AddAnomaly(median_before_anomaly=0.2, start_revision=10),
668        self._AddAnomaly(median_before_anomaly=0.1)
669    ]
670    group = self._AddAlertGroup(
671        anomalies[0],
672        status=alert_group.AlertGroup.Status.untriaged,
673    )
674    self._sheriff_config.patterns = {
675        '*': [
676            subscription.Subscription(name='sheriff', auto_triage_enable=True)
677        ],
678    }
679    w = alert_group_workflow.AlertGroupWorkflow(
680        group.get(),
681        sheriff_config=self._sheriff_config,
682        issue_tracker=self._issue_tracker,
683        revision_info=self._revision_info,
684        config=alert_group_workflow.AlertGroupWorkflow.Config(
685            active_window=datetime.timedelta(days=7),
686            triage_delay=datetime.timedelta(hours=0),
687        ),
688    )
689    w.Process(
690        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
691            now=datetime.datetime.utcnow(),
692            anomalies=ndb.get_multi(anomalies),
693            issue=None,
694        ))
695    self.assertIn('2 regressions', self._issue_tracker.new_bug_args[0])
696    self.assertIn(
697        'Chromium Commit Position: http://test-results.appspot.com/revision_range?start=0&end=100',
698        self._issue_tracker.new_bug_args[1])
699
700  def testTriage_GroupUntriaged_InfAnomaly(self):
701    anomalies = [self._AddAnomaly(median_before_anomaly=0), self._AddAnomaly()]
702    group = self._AddAlertGroup(
703        anomalies[0],
704        status=alert_group.AlertGroup.Status.untriaged,
705    )
706    self._sheriff_config.patterns = {
707        '*': [
708            subscription.Subscription(name='sheriff', auto_triage_enable=True)
709        ],
710    }
711    w = alert_group_workflow.AlertGroupWorkflow(
712        group.get(),
713        sheriff_config=self._sheriff_config,
714        issue_tracker=self._issue_tracker,
715        revision_info=self._revision_info,
716        config=alert_group_workflow.AlertGroupWorkflow.Config(
717            active_window=datetime.timedelta(days=7),
718            triage_delay=datetime.timedelta(hours=0),
719        ),
720    )
721    w.Process(
722        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
723            now=datetime.datetime.utcnow(),
724            anomalies=ndb.get_multi(anomalies),
725            issue=None,
726        ))
727    self.assertIn('inf', self._issue_tracker.new_bug_args[1])
728
729  def testTriage_GroupTriaged_InfAnomaly(self):
730    anomalies = [self._AddAnomaly(median_before_anomaly=0), self._AddAnomaly()]
731    group = self._AddAlertGroup(
732        anomalies[0],
733        issue=self._issue_tracker.issue,
734        status=alert_group.AlertGroup.Status.triaged,
735    )
736    self._sheriff_config.patterns = {
737        '*': [
738            subscription.Subscription(name='sheriff', auto_triage_enable=True)
739        ],
740    }
741    w = alert_group_workflow.AlertGroupWorkflow(
742        group.get(),
743        sheriff_config=self._sheriff_config,
744        issue_tracker=self._issue_tracker,
745    )
746    w.Process(
747        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
748            now=datetime.datetime.utcnow(),
749            anomalies=ndb.get_multi(anomalies),
750            issue=self._issue_tracker.issue,
751        ))
752    self.assertIn('inf', self._issue_tracker.add_comment_args[1])
753    self.assertFalse(self._issue_tracker.add_comment_kwargs['send_email'])
754
755  def testArchive_GroupUntriaged(self):
756    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
757    group = self._AddAlertGroup(
758        anomalies[0],
759        anomalies=anomalies,
760        status=alert_group.AlertGroup.Status.untriaged,
761    )
762    self._sheriff_config.patterns = {
763        '*': [subscription.Subscription(name='sheriff')],
764    }
765    w = alert_group_workflow.AlertGroupWorkflow(
766        group.get(),
767        sheriff_config=self._sheriff_config,
768        issue_tracker=self._issue_tracker,
769        config=alert_group_workflow.AlertGroupWorkflow.Config(
770            active_window=datetime.timedelta(days=0),
771            triage_delay=datetime.timedelta(hours=0),
772        ),
773    )
774    w.Process(
775        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
776            now=datetime.datetime.utcnow(),
777            anomalies=ndb.get_multi(anomalies),
778            issue=None,
779        ))
780    self.assertEqual(False, group.get().active)
781
782  def testArchive_GroupTriaged(self):
783    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
784    group = self._AddAlertGroup(
785        anomalies[0],
786        anomalies=anomalies,
787        issue=self._issue_tracker.issue,
788        status=alert_group.AlertGroup.Status.triaged,
789    )
790    self._issue_tracker.issue.update({
791        'state': 'open',
792    })
793    self._sheriff_config.patterns = {
794        '*': [
795            subscription.Subscription(name='sheriff', auto_triage_enable=True)
796        ],
797    }
798    w = alert_group_workflow.AlertGroupWorkflow(
799        group.get(),
800        sheriff_config=self._sheriff_config,
801        issue_tracker=self._issue_tracker,
802        config=alert_group_workflow.AlertGroupWorkflow.Config(
803            active_window=datetime.timedelta(days=0),
804            triage_delay=datetime.timedelta(hours=0),
805        ),
806    )
807    w.Process(
808        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
809            now=datetime.datetime.utcnow(),
810            anomalies=ndb.get_multi(anomalies),
811            issue=self._issue_tracker.issue,
812        ))
813    self.assertEqual(True, group.get().active)
814
815  def testBisect_GroupTriaged(self):
816    anomalies = [
817        self._AddAnomaly(median_before_anomaly=0.2),
818        self._AddAnomaly(median_before_anomaly=0.1),
819    ]
820    group = self._AddAlertGroup(
821        anomalies[0],
822        issue=self._issue_tracker.issue,
823        status=alert_group.AlertGroup.Status.triaged,
824    )
825    self._issue_tracker.issue.update({
826        'state': 'open',
827    })
828    self._sheriff_config.patterns = {
829        '*': [
830            subscription.Subscription(
831                name='sheriff',
832                auto_triage_enable=True,
833                auto_bisect_enable=True)
834        ],
835    }
836    w = alert_group_workflow.AlertGroupWorkflow(
837        group.get(),
838        sheriff_config=self._sheriff_config,
839        issue_tracker=self._issue_tracker,
840        pinpoint=self._pinpoint,
841        crrev=self._crrev,
842    )
843    w.Process(
844        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
845            now=datetime.datetime.utcnow(),
846            anomalies=ndb.get_multi(anomalies),
847            issue=self._issue_tracker.issue,
848        ))
849    tags = json.loads(self._pinpoint.new_job_request['tags'])
850    self.assertEqual(anomalies[1].urlsafe(), tags['alert'])
851
852    # Tags must be a dict of key/value string pairs.
853    for k, v in tags.items():
854      self.assertIsInstance(k, basestring)
855      self.assertIsInstance(v, basestring)
856
857    self.assertEqual(['123456'], group.get().bisection_ids)
858    self.assertEqual(['Chromeperf-Auto-Bisected'],
859                     self._issue_tracker.add_comment_kwargs['labels'])
860
861  def testBisect_GroupTriaged_MultiSubscriptions(self):
862    anomalies = [
863        self._AddAnomaly(median_before_anomaly=0.2),
864        self._AddAnomaly(median_before_anomaly=0.1),
865    ]
866    group = self._AddAlertGroup(
867        anomalies[0],
868        issue=self._issue_tracker.issue,
869        status=alert_group.AlertGroup.Status.triaged,
870    )
871    self._issue_tracker.issue.update({
872        'state': 'open',
873    })
874    self._sheriff_config.patterns = {
875        '*': [
876            subscription.Subscription(name='sheriff'),
877            subscription.Subscription(
878                name='sheriff_not_bind',
879                auto_triage_enable=True,
880                auto_bisect_enable=True)
881        ],
882    }
883    w = alert_group_workflow.AlertGroupWorkflow(
884        group.get(),
885        sheriff_config=self._sheriff_config,
886        issue_tracker=self._issue_tracker,
887        pinpoint=self._pinpoint,
888        crrev=self._crrev,
889    )
890    w.Process(
891        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
892            now=datetime.datetime.utcnow(),
893            anomalies=ndb.get_multi(anomalies),
894            issue=self._issue_tracker.issue,
895        ))
896    self.assertIsNone(self._pinpoint.new_job_request)
897
898  def testBisect_GroupBisected(self):
899    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
900    group = self._AddAlertGroup(
901        anomalies[0],
902        issue=self._issue_tracker.issue,
903        status=alert_group.AlertGroup.Status.bisected,
904    )
905    self._issue_tracker.issue.update({
906        'state': 'open',
907    })
908    self._sheriff_config.patterns = {
909        '*': [
910            subscription.Subscription(
911                name='sheriff',
912                auto_triage_enable=True,
913                auto_bisect_enable=True)
914        ],
915    }
916    w = alert_group_workflow.AlertGroupWorkflow(
917        group.get(),
918        sheriff_config=self._sheriff_config,
919        issue_tracker=self._issue_tracker,
920        pinpoint=self._pinpoint,
921        crrev=self._crrev,
922    )
923    w.Process(
924        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
925            now=datetime.datetime.utcnow(),
926            anomalies=ndb.get_multi(anomalies),
927            issue=self._issue_tracker.issue,
928        ))
929    self.assertIsNone(self._pinpoint.new_job_request)
930
931  def testBisect_GroupTriaged_NoRecovered(self):
932    anomalies = [
933        self._AddAnomaly(
934            median_before_anomaly=0.1, median_after_anomaly=1.0,
935            recovered=True),
936        self._AddAnomaly(median_before_anomaly=0.2, median_after_anomaly=1.0),
937    ]
938    group = self._AddAlertGroup(
939        anomalies[1],
940        issue=self._issue_tracker.issue,
941        status=alert_group.AlertGroup.Status.triaged,
942        anomalies=anomalies,
943    )
944    self._issue_tracker.issue.update({
945        'state': 'open',
946    })
947    self._sheriff_config.patterns = {
948        '*': [
949            subscription.Subscription(
950                name='sheriff',
951                auto_triage_enable=True,
952                auto_bisect_enable=True)
953        ],
954    }
955    w = alert_group_workflow.AlertGroupWorkflow(
956        group.get(),
957        sheriff_config=self._sheriff_config,
958        issue_tracker=self._issue_tracker,
959        pinpoint=self._pinpoint,
960        crrev=self._crrev,
961    )
962    w.Process(
963        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
964            now=datetime.datetime.utcnow(),
965            anomalies=ndb.get_multi(anomalies),
966            issue=self._issue_tracker.issue,
967        ))
968    self.assertIsNotNone(self._pinpoint.new_job_request)
969    self.assertEqual(group.get().status, alert_group.AlertGroup.Status.bisected)
970
971    # Check that we bisected the anomaly that is not recovered.
972    recovered_anomaly = anomalies[0].get()
973    bisected_anomaly = anomalies[1].get()
974    self.assertNotEqual(recovered_anomaly.pinpoint_bisects, ['123456'])
975    self.assertEqual(bisected_anomaly.pinpoint_bisects, ['123456'])
976
977  def testBisect_GroupTriaged_NoIgnored(self):
978    anomalies = [
979        # This anomaly is manually ignored.
980        self._AddAnomaly(
981            median_before_anomaly=0.1, median_after_anomaly=1.0, bug_id=-2),
982        self._AddAnomaly(
983            median_before_anomaly=0.2,
984            median_after_anomaly=1.0,
985            start_revision=20),
986    ]
987    group = self._AddAlertGroup(
988        anomalies[1],
989        issue=self._issue_tracker.issue,
990        status=alert_group.AlertGroup.Status.triaged,
991        anomalies=anomalies,
992    )
993    self._issue_tracker.issue.update({
994        'state': 'open',
995    })
996    self._sheriff_config.patterns = {
997        '*': [
998            subscription.Subscription(
999                name='sheriff',
1000                auto_triage_enable=True,
1001                auto_bisect_enable=True)
1002        ],
1003    }
1004    w = alert_group_workflow.AlertGroupWorkflow(
1005        group.get(),
1006        sheriff_config=self._sheriff_config,
1007        issue_tracker=self._issue_tracker,
1008        pinpoint=self._pinpoint,
1009        crrev=self._crrev,
1010    )
1011    w.Process(
1012        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1013            now=datetime.datetime.utcnow(),
1014            anomalies=ndb.get_multi(anomalies),
1015            issue=self._issue_tracker.issue,
1016        ))
1017    self.assertIsNotNone(self._pinpoint.new_job_request)
1018    self.assertEqual(self._pinpoint.new_job_request['bug_id'], 12345)
1019    self.assertEqual(group.get().status, alert_group.AlertGroup.Status.bisected)
1020
1021    # Check that we bisected the anomaly that is not ignored.
1022    ignored_anomaly = anomalies[0].get()
1023    bisected_anomaly = anomalies[1].get()
1024    self.assertNotEqual(ignored_anomaly.pinpoint_bisects, ['123456'])
1025    self.assertEqual(bisected_anomaly.pinpoint_bisects, ['123456'])
1026
1027  def testBisect_GroupTriaged_AlertWithBug(self):
1028    anomalies = [
1029        self._AddAnomaly(median_before_anomaly=0.2),
1030        self._AddAnomaly(
1031            median_before_anomaly=0.1,
1032            bug_id=12340,
1033            project_id='v8',
1034        ),
1035    ]
1036    group = self._AddAlertGroup(
1037        anomalies[0],
1038        issue=self._issue_tracker.issue,
1039        status=alert_group.AlertGroup.Status.triaged,
1040    )
1041    self._issue_tracker.issue.update({
1042        'state': 'open',
1043    })
1044    self._sheriff_config.patterns = {
1045        '*': [
1046            subscription.Subscription(
1047                name='sheriff',
1048                auto_triage_enable=True,
1049                auto_bisect_enable=True)
1050        ],
1051    }
1052    w = alert_group_workflow.AlertGroupWorkflow(
1053        group.get(),
1054        sheriff_config=self._sheriff_config,
1055        issue_tracker=self._issue_tracker,
1056        pinpoint=self._pinpoint,
1057        crrev=self._crrev,
1058    )
1059    w.Process(
1060        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1061            now=datetime.datetime.utcnow(),
1062            anomalies=ndb.get_multi(anomalies),
1063            issue=self._issue_tracker.issue,
1064        ))
1065    self.assertEqual(self._issue_tracker.bug_id,
1066                     self._pinpoint.new_job_request['bug_id'])
1067    self.assertEqual('chromium', self._pinpoint.new_job_request['project'])
1068    self.assertEqual(['123456'], group.get().bisection_ids)
1069
1070  def testBisect_GroupTriaged_MultiBot(self):
1071    anomalies = [
1072        self._AddAnomaly(
1073            test='master/bot1/test_suite/measurement/test_case1',
1074            median_before_anomaly=0.3,
1075        ),
1076        self._AddAnomaly(
1077            test='master/bot1/test_suite/measurement/test_case2',
1078            median_before_anomaly=0.2,
1079        ),
1080        self._AddAnomaly(
1081            test='master/bot2/test_suite/measurement/test_case2',
1082            median_before_anomaly=0.1,
1083        ),
1084    ]
1085    group = self._AddAlertGroup(
1086        anomalies[0],
1087        issue=self._issue_tracker.issue,
1088        status=alert_group.AlertGroup.Status.triaged,
1089    )
1090    self._issue_tracker.issue.update({
1091        'state': 'open',
1092    })
1093    self._sheriff_config.patterns = {
1094        '*': [
1095            subscription.Subscription(
1096                name='sheriff',
1097                auto_triage_enable=True,
1098                auto_bisect_enable=True)
1099        ],
1100    }
1101    w = alert_group_workflow.AlertGroupWorkflow(
1102        group.get(),
1103        sheriff_config=self._sheriff_config,
1104        issue_tracker=self._issue_tracker,
1105        pinpoint=self._pinpoint,
1106        crrev=self._crrev,
1107    )
1108    w.Process(
1109        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1110            now=datetime.datetime.utcnow(),
1111            anomalies=ndb.get_multi(anomalies),
1112            issue=self._issue_tracker.issue,
1113        ))
1114    self.assertEqual(
1115        anomalies[1].urlsafe(),
1116        json.loads(self._pinpoint.new_job_request['tags'])['alert'])
1117    self.assertEqual(['123456'], group.get().bisection_ids)
1118
1119  def testBisect_GroupTriaged_MultiBot_PartInf(self):
1120    anomalies = [
1121        self._AddAnomaly(
1122            test='master/bot1/test_suite/measurement/test_case1',
1123            median_before_anomaly=0.0,
1124        ),
1125        self._AddAnomaly(
1126            test='master/bot1/test_suite/measurement/test_case2',
1127            median_before_anomaly=0.2,
1128        ),
1129        self._AddAnomaly(
1130            test='master/bot2/test_suite/measurement/test_case2',
1131            median_before_anomaly=0.1,
1132        ),
1133    ]
1134    group = self._AddAlertGroup(
1135        anomalies[0],
1136        issue=self._issue_tracker.issue,
1137        status=alert_group.AlertGroup.Status.triaged,
1138    )
1139    self._issue_tracker.issue.update({
1140        'state': 'open',
1141    })
1142    self._sheriff_config.patterns = {
1143        '*': [
1144            subscription.Subscription(
1145                name='sheriff',
1146                auto_triage_enable=True,
1147                auto_bisect_enable=True)
1148        ],
1149    }
1150    w = alert_group_workflow.AlertGroupWorkflow(
1151        group.get(),
1152        sheriff_config=self._sheriff_config,
1153        issue_tracker=self._issue_tracker,
1154        pinpoint=self._pinpoint,
1155        crrev=self._crrev,
1156    )
1157    w.Process(
1158        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1159            now=datetime.datetime.utcnow(),
1160            anomalies=ndb.get_multi(anomalies),
1161            issue=self._issue_tracker.issue,
1162        ))
1163    self.assertEqual(
1164        anomalies[1].urlsafe(),
1165        json.loads(self._pinpoint.new_job_request['tags'])['alert'])
1166    self.assertEqual(['123456'], group.get().bisection_ids)
1167
1168  def testBisect_GroupTriaged_MultiBot_AllInf(self):
1169    anomalies = [
1170        self._AddAnomaly(
1171            test='master/bot1/test_suite/measurement/test_case1',
1172            median_before_anomaly=0.0,
1173            median_after_anomaly=1.0,
1174        ),
1175        self._AddAnomaly(
1176            test='master/bot1/test_suite/measurement/test_case2',
1177            median_before_anomaly=0.0,
1178            median_after_anomaly=2.0,
1179        ),
1180        self._AddAnomaly(
1181            test='master/bot2/test_suite/measurement/test_case2',
1182            median_before_anomaly=0.1,
1183        ),
1184    ]
1185    group = self._AddAlertGroup(
1186        anomalies[0],
1187        issue=self._issue_tracker.issue,
1188        status=alert_group.AlertGroup.Status.triaged,
1189    )
1190    self._issue_tracker.issue.update({
1191        'state': 'open',
1192    })
1193    self._sheriff_config.patterns = {
1194        '*': [
1195            subscription.Subscription(
1196                name='sheriff',
1197                auto_triage_enable=True,
1198                auto_bisect_enable=True)
1199        ],
1200    }
1201    w = alert_group_workflow.AlertGroupWorkflow(
1202        group.get(),
1203        sheriff_config=self._sheriff_config,
1204        issue_tracker=self._issue_tracker,
1205        pinpoint=self._pinpoint,
1206        crrev=self._crrev,
1207    )
1208    w.Process(
1209        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1210            now=datetime.datetime.utcnow(),
1211            anomalies=ndb.get_multi(anomalies),
1212            issue=self._issue_tracker.issue,
1213        ))
1214    self.assertEqual(
1215        anomalies[1].urlsafe(),
1216        json.loads(self._pinpoint.new_job_request['tags'])['alert'])
1217    self.assertEqual(['123456'], group.get().bisection_ids)
1218
1219  def testBisect_GroupTriaged_AlertBisected(self):
1220    anomalies = [
1221        self._AddAnomaly(
1222            test='master/bot1/test_suite/measurement/test_case1',
1223            pinpoint_bisects=['abcdefg'],
1224            median_before_anomaly=0.2,
1225        ),
1226        self._AddAnomaly(
1227            test='master/bot1/test_suite/measurement/test_case2',
1228            pinpoint_bisects=['abcdef'],
1229            median_before_anomaly=0.1,
1230        ),
1231    ]
1232    group = self._AddAlertGroup(
1233        anomalies[0],
1234        issue=self._issue_tracker.issue,
1235        status=alert_group.AlertGroup.Status.triaged,
1236        bisection_ids=['abcdef'],
1237    )
1238    self._issue_tracker.issue.update({
1239        'state': 'open',
1240    })
1241    self._sheriff_config.patterns = {
1242        '*': [
1243            subscription.Subscription(
1244                name='sheriff',
1245                auto_triage_enable=True,
1246                auto_bisect_enable=True)
1247        ],
1248    }
1249    w = alert_group_workflow.AlertGroupWorkflow(
1250        group.get(),
1251        sheriff_config=self._sheriff_config,
1252        issue_tracker=self._issue_tracker,
1253        pinpoint=self._pinpoint,
1254        crrev=self._crrev,
1255    )
1256    w.Process(
1257        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1258            now=datetime.datetime.utcnow(),
1259            anomalies=ndb.get_multi(anomalies),
1260            issue=self._issue_tracker.issue,
1261        ))
1262    self.assertEqual(
1263        anomalies[0].urlsafe(),
1264        json.loads(self._pinpoint.new_job_request['tags'])['alert'])
1265    self.assertItemsEqual(['abcdef', '123456'], group.get().bisection_ids)
1266
1267  def testBisect_GroupTriaged_CrrevFailed(self):
1268    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
1269    group = self._AddAlertGroup(
1270        anomalies[0],
1271        issue=self._issue_tracker.issue,
1272        status=alert_group.AlertGroup.Status.triaged,
1273    )
1274    self._issue_tracker.issue.update({
1275        'state': 'open',
1276    })
1277    self._crrev.SetFailure()
1278    self._sheriff_config.patterns = {
1279        '*': [
1280            subscription.Subscription(
1281                name='sheriff',
1282                auto_triage_enable=True,
1283                auto_bisect_enable=True)
1284        ],
1285    }
1286    w = alert_group_workflow.AlertGroupWorkflow(
1287        group.get(),
1288        sheriff_config=self._sheriff_config,
1289        issue_tracker=self._issue_tracker,
1290        pinpoint=self._pinpoint,
1291        crrev=self._crrev,
1292    )
1293    w.Process(
1294        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1295            now=datetime.datetime.utcnow(),
1296            anomalies=ndb.get_multi(anomalies),
1297            issue=self._issue_tracker.issue,
1298        ))
1299    self.assertEqual(alert_group.AlertGroup.Status.bisected, group.get().status)
1300    self.assertEqual([], group.get().bisection_ids)
1301    self.assertEqual(['Chromeperf-Auto-NeedsAttention'],
1302                     self._issue_tracker.add_comment_kwargs['labels'])
1303
1304  def testBisect_GroupTriaged_PinpointFailed(self):
1305    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
1306    group = self._AddAlertGroup(
1307        anomalies[0],
1308        issue=self._issue_tracker.issue,
1309        status=alert_group.AlertGroup.Status.triaged,
1310    )
1311    self._issue_tracker.issue.update({
1312        'state': 'open',
1313    })
1314    self._pinpoint.SetFailure()
1315    self._sheriff_config.patterns = {
1316        '*': [
1317            subscription.Subscription(
1318                name='sheriff',
1319                auto_triage_enable=True,
1320                auto_bisect_enable=True)
1321        ],
1322    }
1323    w = alert_group_workflow.AlertGroupWorkflow(
1324        group.get(),
1325        sheriff_config=self._sheriff_config,
1326        issue_tracker=self._issue_tracker,
1327        pinpoint=self._pinpoint,
1328        crrev=self._crrev,
1329    )
1330    w.Process(
1331        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1332            now=datetime.datetime.utcnow(),
1333            anomalies=ndb.get_multi(anomalies),
1334            issue=self._issue_tracker.issue,
1335        ))
1336    self.assertEqual(alert_group.AlertGroup.Status.bisected, group.get().status)
1337    self.assertEqual([], group.get().bisection_ids)
1338    self.assertEqual(['Chromeperf-Auto-NeedsAttention'],
1339                     self._issue_tracker.add_comment_kwargs['labels'])
1340
1341  def testBisect_SingleCL(self):
1342    anomalies = [
1343        self._AddAnomaly(
1344            # Current implementation requires that a revision string is between
1345            # 5 and 7 digits long.
1346            start_revision=11111,
1347            end_revision=11111,
1348            test='ChromiumPerf/some-bot/some-benchmark/some-metric/some-story')
1349    ]
1350    group = self._AddAlertGroup(
1351        anomalies[0],
1352        issue=self._issue_tracker.issue,
1353        status=alert_group.AlertGroup.Status.triaged)
1354    self._sheriff_config.patterns = {
1355        '*': [
1356            subscription.Subscription(
1357                name='sheriff',
1358                auto_triage_enable=True,
1359                auto_bisect_enable=True)
1360        ]
1361    }
1362    # Here we are simulating that a gitiles service will respond to a specific
1363    # repository URL (the format is not important) and can map a commit (40
1364    # hexadecimal characters) to some commit information.
1365    self._gitiles._repo_commit_list.update({
1366        'git://chromium': {
1367            'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa': {
1368                'author': {
1369                    'email': 'author@chromium.org',
1370                },
1371                'message': 'This is some commit.\n\nWith some details.',
1372            }
1373        }
1374    })
1375
1376    # We are also seeding some repository information to let us set which
1377    # repository URL is being used to look up data from a gitiles service.
1378    namespaced_stored_object.Set('repositories', {
1379        'chromium': {
1380            'repository_url': 'git://chromium'
1381        },
1382    })
1383
1384    # Current implementation requires that a git hash is 40 characters of
1385    # hexadecimal digits.
1386    self._crrev.SetSuccess('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa')
1387    w = alert_group_workflow.AlertGroupWorkflow(
1388        group.get(),
1389        sheriff_config=self._sheriff_config,
1390        issue_tracker=self._issue_tracker,
1391        pinpoint=self._pinpoint,
1392        crrev=self._crrev,
1393        gitiles=self._gitiles)
1394    w.Process(
1395        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1396            now=datetime.datetime.utcnow(),
1397            anomalies=ndb.get_multi(anomalies),
1398            issue=self._issue_tracker.issue))
1399    self.assertEqual(alert_group.AlertGroup.Status.bisected, group.get().status)
1400    self.assertEqual([], group.get().bisection_ids)
1401    self.assertEqual(['Chromeperf-Auto-Assigned'],
1402                     self._issue_tracker.add_comment_kwargs['labels'])
1403    self.assertIn(('Assigning to author@chromium.org because this is the '
1404                   'only CL in range:'),
1405                  self._issue_tracker.add_comment_args[1])
1406
1407  def testBisect_ExplicitOptOut(self):
1408    anomalies = [self._AddAnomaly(), self._AddAnomaly()]
1409    group = self._AddAlertGroup(
1410        anomalies[0],
1411        issue=self._issue_tracker.issue,
1412        status=alert_group.AlertGroup.Status.triaged,
1413    )
1414    self._issue_tracker.issue.update({
1415        'state':
1416            'open',
1417        'labels':
1418            self._issue_tracker.issue.get('labels') +
1419            ['Chromeperf-Auto-BisectOptOut']
1420    })
1421    self._sheriff_config.patterns = {
1422        '*': [
1423            subscription.Subscription(
1424                name='sheriff',
1425                auto_triage_enable=True,
1426                auto_bisect_enable=True)
1427        ],
1428    }
1429    w = alert_group_workflow.AlertGroupWorkflow(
1430        group.get(),
1431        sheriff_config=self._sheriff_config,
1432        issue_tracker=self._issue_tracker,
1433        pinpoint=self._pinpoint,
1434        crrev=self._crrev,
1435    )
1436    self.assertIn('Chromeperf-Auto-BisectOptOut',
1437                  self._issue_tracker.issue.get('labels'))
1438    w.Process(
1439        update=alert_group_workflow.AlertGroupWorkflow.GroupUpdate(
1440            now=datetime.datetime.utcnow(),
1441            anomalies=ndb.get_multi(anomalies),
1442            issue=self._issue_tracker.issue,
1443        ))
1444    self.assertIsNone(self._pinpoint.new_job_request)
1445