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