1edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# Copyright 2015 The Chromium Authors. All rights reserved.
2edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# Use of this source code is governed by a BSD-style license that can be
3edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair# found in the LICENSE file.
4edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
5edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairimport unittest
6edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
7edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairimport mock
8edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairimport webapp2
9edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairimport webtest
10edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
11cef7893435aa41160dd1255c43cb8498279738ccChris Craik# pylint: disable=unused-import
12cef7893435aa41160dd1255c43cb8498279738ccChris Craikfrom dashboard import mock_oauth2_decorator
13cef7893435aa41160dd1255c43cb8498279738ccChris Craik# pylint: enable=unused-import
14cef7893435aa41160dd1255c43cb8498279738ccChris Craik
15edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairfrom dashboard import associate_alerts
16cef7893435aa41160dd1255c43cb8498279738ccChris Craikfrom dashboard import issue_tracker_service
17edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairfrom dashboard import testing_common
18edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairfrom dashboard import utils
19edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairfrom dashboard.models import anomaly
20edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairfrom dashboard.models import sheriff
21edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairfrom dashboard.models import stoppage_alert
22edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
23edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
24edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairclass AssociateAlertsTest(testing_common.TestCase):
25edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
26edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def setUp(self):
27edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    super(AssociateAlertsTest, self).setUp()
28edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    app = webapp2.WSGIApplication([(
29edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts', associate_alerts.AssociateAlertsHandler)])
30edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.testapp = webtest.TestApp(app)
31edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    testing_common.SetSheriffDomains(['chromium.org'])
32edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.SetCurrentUser('foo@chromium.org', is_admin=True)
33edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
34edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def _AddSheriff(self):
35edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    """Adds a Sheriff and returns its key."""
36edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    return sheriff.Sheriff(
37edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        id='Chromium Perf Sheriff', email='sullivan@google.com').put()
38edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
39edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def _AddTests(self):
40edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    """Adds sample Tests and returns a list of their keys."""
41edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    testing_common.AddTests(['ChromiumGPU'], ['linux-release'], {
42edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        'scrolling-benchmark': {
43edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            'first_paint': {},
44edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            'mean_frame_time': {},
45edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        }
46edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    })
47edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    return map(utils.TestKey, [
48edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        'ChromiumGPU/linux-release/scrolling-benchmark/first_paint',
49edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        'ChromiumGPU/linux-release/scrolling-benchmark/mean_frame_time',
50edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    ])
51edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
52edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def _AddAnomalies(self):
53edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    """Adds sample Anomaly data and returns a dict of revision to key."""
54edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    sheriff_key = self._AddSheriff()
55edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    test_keys = self._AddTests()
56edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = {}
57edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
58edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Add anomalies to the two tests alternately.
59edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    for end_rev in range(10000, 10120, 10):
60edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      test_key = test_keys[0] if end_rev % 20 == 0 else test_keys[1]
61edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      anomaly_key = anomaly.Anomaly(
62edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair          start_revision=(end_rev - 5), end_revision=end_rev, test=test_key,
63edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair          median_before_anomaly=100, median_after_anomaly=200,
64edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair          sheriff=sheriff_key).put()
65edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      key_map[end_rev] = anomaly_key.urlsafe()
66edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
67edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Add an anomaly that overlaps.
68edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    anomaly_key = anomaly.Anomaly(
69edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        start_revision=9990, end_revision=9996, test=test_keys[0],
70edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        median_before_anomaly=100, median_after_anomaly=200,
71edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        sheriff=sheriff_key).put()
72edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map[9996] = anomaly_key.urlsafe()
73edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
74edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Add an anomaly that overlaps and has bug ID.
75edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    anomaly_key = anomaly.Anomaly(
76edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        start_revision=9990, end_revision=9997, test=test_keys[0],
77edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        median_before_anomaly=100, median_after_anomaly=200,
78edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        sheriff=sheriff_key, bug_id=12345).put()
79edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map[9997] = anomaly_key.urlsafe()
80edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    return key_map
81edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
82edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_NoKeys_ShowsError(self):
83edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get('/associate_alerts')
84edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('<div class="error">', response.body)
85edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
86edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_SameAsPost(self):
87edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    get_response = self.testapp.get('/associate_alerts')
88edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    post_response = self.testapp.post('/associate_alerts')
89edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertEqual(get_response.body, post_response.body)
90edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
91edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_InvalidBugId_ShowsError(self):
92edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = self._AddAnomalies()
93edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get(
94edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts?keys=%s&bug_id=foo' % key_map[9996])
95edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('<div class="error">', response.body)
96edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('Invalid bug ID', response.body)
97edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
98cef7893435aa41160dd1255c43cb8498279738ccChris Craik  # Mocks fetching bugs from issue tracker.
99cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @mock.patch('issue_tracker_service.discovery.build', mock.MagicMock())
100cef7893435aa41160dd1255c43cb8498279738ccChris Craik  @mock.patch.object(
101cef7893435aa41160dd1255c43cb8498279738ccChris Craik      issue_tracker_service.IssueTrackerService, 'List',
102cef7893435aa41160dd1255c43cb8498279738ccChris Craik      mock.MagicMock(return_value={
103cef7893435aa41160dd1255c43cb8498279738ccChris Craik          'items': [
104cef7893435aa41160dd1255c43cb8498279738ccChris Craik              {
105cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'id': 12345,
106cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'summary': '5% regression in bot/suite/x at 10000:20000',
107cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'state': 'open',
108cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'status': 'New',
109cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'author': {'name': 'exam...@google.com'},
110cef7893435aa41160dd1255c43cb8498279738ccChris Craik              },
111cef7893435aa41160dd1255c43cb8498279738ccChris Craik              {
112cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'id': 13579,
113cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'summary': '1% regression in bot/suite/y at 10000:20000',
114cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'state': 'closed',
115cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'status': 'WontFix',
116cef7893435aa41160dd1255c43cb8498279738ccChris Craik                  'author': {'name': 'exam...@google.com'},
117cef7893435aa41160dd1255c43cb8498279738ccChris Craik              },
118cef7893435aa41160dd1255c43cb8498279738ccChris Craik          ]}))
119edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_NoBugId_ShowsDialog(self):
120edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # When a GET request is made with some anomaly keys but no bug ID,
121edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # A HTML form is shown for the user to input a bug number.
122edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = self._AddAnomalies()
123edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get('/associate_alerts?keys=%s' % key_map[10000])
124edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The response contains a table of recent bugs and a form.
125edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('12345', response.body)
126edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('13579', response.body)
127edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('<form', response.body)
128edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
129edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_WithBugId_AlertIsAssociatedWithBugId(self):
130edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # When the bug ID is given and the alerts overlap, then the Anomaly
131cef7893435aa41160dd1255c43cb8498279738ccChris Craik    # entities are updated and there is a response indicating success.
132edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = self._AddAnomalies()
133edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get(
134edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts?keys=%s,%s&bug_id=12345' % (
135edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            key_map[9996], key_map[10000]))
136edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The response page should have a bug number.
137edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('12345', response.body)
138edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The Anomaly entities should be updated.
139edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    for anomaly_entity in anomaly.Anomaly.query().fetch():
140edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      if anomaly_entity.end_revision in (10000, 9996):
141edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        self.assertEqual(12345, anomaly_entity.bug_id)
142edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      elif anomaly_entity.end_revision != 9997:
143edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        self.assertIsNone(anomaly_entity.bug_id)
144edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
145edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_WithStoppageAlert_ChangesAlertBugId(self):
146edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    test_keys = self._AddTests()
147edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    rows = testing_common.AddRows(utils.TestPath(test_keys[0]), {10, 20})
148edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    alert_key = stoppage_alert.CreateStoppageAlert(
149edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        test_keys[0].get(), rows[0]).put()
150edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.testapp.get(
151edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts?bug_id=123&keys=%s' % alert_key.urlsafe())
152edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertEqual(123, alert_key.get().bug_id)
153edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
154edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_TargetBugHasNoAlerts_DoesNotAskForConfirmation(self):
155edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Associating alert with bug ID that has no alerts is always OK.
156edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = self._AddAnomalies()
157edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get(
158edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts?keys=%s,%s&bug_id=578' % (
159edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            key_map[9996], key_map[10000]))
160edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The response page should have a bug number.
161edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('578', response.body)
162edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The Anomaly entities should be updated.
163edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertEqual(
164edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        578, anomaly.Anomaly.query(
165edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            anomaly.Anomaly.end_revision == 9996).get().bug_id)
166edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertEqual(
167edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        578, anomaly.Anomaly.query(
168edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            anomaly.Anomaly.end_revision == 10000).get().bug_id)
169edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
170edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_NonOverlappingAlerts_AsksForConfirmation(self):
171edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Associating alert with bug ID that contains non-overlapping revision
172edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # ranges should show a confirmation page.
173edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = self._AddAnomalies()
174edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get(
175edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts?keys=%s,%s&bug_id=12345' % (
176edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            key_map[10000], key_map[10010]))
177edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The response page should show confirmation page.
178edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('Do you want to continue?', response.body)
179edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The Anomaly entities should not be updated.
180edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    for anomaly_entity in anomaly.Anomaly.query().fetch():
181edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      if anomaly_entity.end_revision != 9997:
182edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        self.assertIsNone(anomaly_entity.bug_id)
183edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
184edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testGet_WithConfirm_AssociatesWithNewBugId(self):
185edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Associating alert with bug ID and with confirmed non-overlapping revision
186edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # range should update alert with bug ID.
187edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    key_map = self._AddAnomalies()
188edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    response = self.testapp.get(
189edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        '/associate_alerts?confirm=true&keys=%s,%s&bug_id=12345' % (
190edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            key_map[10000], key_map[10010]))
191edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The response page should have the bug number.
192edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIn('12345', response.body)
193edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # The Anomaly entities should be updated.
194edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    for anomaly_entity in anomaly.Anomaly.query().fetch():
195edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      if anomaly_entity.end_revision in (10000, 10010):
196edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        self.assertEqual(12345, anomaly_entity.bug_id)
197edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair      elif anomaly_entity.end_revision != 9997:
198edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        self.assertIsNone(anomaly_entity.bug_id)
199edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
200edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testRevisionRangeFromSummary(self):
201edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # If the summary is in the expected format, a pair is returned.
202edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertEqual(
203edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        (10000, 10500),
204edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        associate_alerts._RevisionRangeFromSummary(
205edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            '1% regression in bot/my_suite/test at 10000:10500'))
206edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # Otherwise None is returned.
207edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertIsNone(
208edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair        associate_alerts._RevisionRangeFromSummary(
209edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair            'Regression in rev ranges 12345 to 20000'))
210edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
211edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testRangesOverlap_NonOverlapping_ReturnsFalse(self):
212edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertFalse(associate_alerts._RangesOverlap((1, 5), (6, 9)))
213edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertFalse(associate_alerts._RangesOverlap((6, 9), (1, 5)))
214edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
215edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testRangesOverlap_NoneGiven_ReturnsFalse(self):
216edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertFalse(associate_alerts._RangesOverlap((1, 5), None))
217edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertFalse(associate_alerts._RangesOverlap(None, (1, 5)))
218edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertFalse(associate_alerts._RangesOverlap(None, None))
219edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
220edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testRangesOverlap_OneIncludesOther_ReturnsTrue(self):
221edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    # True if one range envelopes the other.
222edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertTrue(associate_alerts._RangesOverlap((1, 9), (2, 5)))
223edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertTrue(associate_alerts._RangesOverlap((2, 5), (1, 9)))
224edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
225edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testRangesOverlap_PartlyOverlap_ReturnsTrue(self):
226edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertTrue(associate_alerts._RangesOverlap((1, 6), (5, 9)))
227edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertTrue(associate_alerts._RangesOverlap((5, 9), (1, 6)))
228edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
229edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  def testRangesOverlap_CommonBoundary_ReturnsTrue(self):
230edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertTrue(associate_alerts._RangesOverlap((1, 6), (6, 9)))
231edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair    self.assertTrue(associate_alerts._RangesOverlap((6, 9), (1, 6)))
232edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
233edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair
234edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclairif __name__ == '__main__':
235edfe2194ee8a857cc1e78b4e4020f9b5e7210029Dan Sinclair  unittest.main()
236