blob: 2f2a75ae074a1aaab5f92554da5f45a44657a4b3 [file] [log] [blame]
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import datetime
import mock
import textwrap
from google.appengine.ext import ndb
from googleapiclient.errors import HttpError
from libs import time_util
from model.flake.analysis.data_point import DataPoint
from model.flake.analysis.flake_culprit import FlakeCulprit
from model.flake.analysis.master_flake_analysis import MasterFlakeAnalysis
from model.flake.detection.flake_occurrence import FlakeOccurrence
from model.flake.flake import Flake
from model.flake.flake_issue import FlakeIssue
from model.flake.flake_type import FlakeType
from monorail_api import Issue
from services import flake_issue_util
from services import monorail_util
from services.issue_generator import FlakeAnalysisIssueGenerator
from services.issue_generator import FlakyTestIssueGenerator
from waterfall.test.wf_testcase import WaterfallTestCase
class TestIssueGenerator(FlakyTestIssueGenerator):
"""A FlakyTestIssueGenerator used for testing."""
def __init__(self,
step_name='step',
test_name='suite.test',
test_label_name='*/suite.test/*'):
super(TestIssueGenerator, self).__init__()
self.step_name = step_name
self.test_name = test_name
self.test_label_name = test_label_name
#self._previous_tracking_bug_id = None
def GetStepName(self):
return self.step_name
def GetTestName(self):
return self.test_name
def GetTestLabelName(self):
return self.test_label_name
def GetDescription(self):
previous_tracking_bug_id = self.GetPreviousTrackingBugId()
if previous_tracking_bug_id:
return ('description with previous tracking bug id: %s.' %
previous_tracking_bug_id)
return 'description without previous tracking bug id.'
def GetComment(self):
previous_tracking_bug_id = self.GetPreviousTrackingBugId()
if previous_tracking_bug_id:
return ('comment with previous tracking bug id: %s.' %
previous_tracking_bug_id)
return 'comment without previous tracking bug id.'
def ShouldRestoreChromiumSheriffLabel(self):
# Sets to False as default value, if need to test this control flow, please
# mock this method.
return False
def GetLabels(self):
return ['label1', 'Sheriff-Chromium']
class FlakeReportUtilTest(WaterfallTestCase):
def _CreateFlake(self,
normalized_step_name,
normalized_test_name,
test_label_name,
test_suite_name=None):
flake = Flake.Create(
luci_project='chromium',
normalized_step_name=normalized_step_name,
normalized_test_name=normalized_test_name,
test_label_name=test_label_name)
if test_suite_name:
flake.tags.append('suite::%s' % test_suite_name)
flake.put()
return flake
def _CreateFlakeOccurrence(self,
build_id,
step_ui_name,
test_name,
gerrit_cl_id,
parent_flake_key,
flake_type=FlakeType.CQ_FALSE_REJECTION):
flake_occurrence = FlakeOccurrence.Create(
flake_type=flake_type,
build_id=build_id,
step_ui_name=step_ui_name,
test_name=test_name,
gerrit_cl_id=gerrit_cl_id,
parent_flake_key=parent_flake_key,
luci_project='chromium',
luci_bucket='try',
luci_builder='linux_chromium_rel_ng',
legacy_master_name='tryserver.chromium.linux',
legacy_build_number=999,
time_happened=datetime.datetime.utcnow())
flake_occurrence.put()
return flake_occurrence
def _GetDatetimeHoursAgo(self, hours):
"""Returns the utc datetime of some hours ago."""
return time_util.GetUTCNow() - datetime.timedelta(hours=hours)
def setUp(self):
super(FlakeReportUtilTest, self).setUp()
self.UpdateUnitTestConfigSettings('flake_detection_settings',
{'report_flakes_to_flake_analyzer': True})
patcher = mock.patch.object(
time_util, 'GetUTCNow', return_value=datetime.datetime(2018, 1, 2))
self.addCleanup(patcher.stop)
patcher.start()
self.flake = self._CreateFlake('step', 'test', 'test_label')
self._CreateFlakeOccurrence(111, 'step1', 'test1', 98765, self.flake.key)
self._CreateFlakeOccurrence(
222,
'step2',
'test2',
98764,
self.flake.key,
flake_type=FlakeType.RETRY_WITH_PATCH)
self._CreateFlakeOccurrence(333, 'step3', 'test3', 98763, self.flake.key)
# This test tests that getting flakes with enough occurrences works properly.
def testGetFlakesWithEnoughOccurrences(self):
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
self.assertEqual(1, len(flakes_with_occurrences))
self.assertEqual(3, len(flakes_with_occurrences[0][1]))
self.assertIsNone(flakes_with_occurrences[0][2])
# This test tests that in order for a flake to have enough occurrences, there
# needs to be at least 3 (min_required_impacted_cls_per_day) occurrences
# with different CLs, and different patchsets of the same CL are only counted
# once.
def testMinimumRequiredFalselyRejectedCLs(self):
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
for occurrence in occurrences:
occurrence.gerrit_cl_id = 565656
occurrence.put()
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
self.assertEqual(0, len(flakes_with_occurrences))
# This test tests that occurrences happened more than one day ago are ignored.
def testIgnoreOutdatedOccurrences(self):
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
occurrences[2].time_happened = self._GetDatetimeHoursAgo(25)
occurrences[2].put()
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
self.assertEqual(0, len(flakes_with_occurrences))
# This test tests that if the number of issues created or updated within the
# past 24 hours has already reached a limit, then no issues can be created or
# updated.
@mock.patch.object(flake_issue_util, 'CreateOrUpdateIssue')
def testCreateOrUpdateIssuesPerDayLimit(self, mock_update_or_create_bug):
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
groups_wo_issue, groups_w_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs(flakes_with_occurrences)
)
self.assertEqual(1, len(flakes_with_occurrences))
self.assertEqual(3, len(flakes_with_occurrences[0][1]))
self.UpdateUnitTestConfigSettings('action_settings',
{'max_flake_bug_updates_per_day': 0})
flake_issue_util.ReportFlakesToMonorail(groups_wo_issue, groups_w_issue)
self.assertFalse(mock_update_or_create_bug.called)
# This test tests that flakes that were updated within 24h are ignored.
def testIgnoreFlakesAlreadyUpdatedWith24h(self):
flake = Flake.query().fetch()[0]
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=900)
flake_issue.last_updated_time_by_flake_detection = (
self._GetDatetimeHoursAgo(5))
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
occurrences[2].time_detected = self._GetDatetimeHoursAgo(10)
occurrences[2].put()
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
self.assertEqual(0, len(flakes_with_occurrences))
# This test tests that the program doesn't crash when a flake has no
# unreported occurrences.
def testFlakeHasNoUnreportedOccurrences(self):
flake = Flake.query().fetch()[0]
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=900)
flake_issue.last_updated_time_by_flake_detection = (
self._GetDatetimeHoursAgo(5))
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
for occurrence in occurrences:
occurrence.time_detected = self._GetDatetimeHoursAgo(10)
occurrence.put()
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
self.assertEqual(0, len(flakes_with_occurrences))
# This test tests a flake has some unreported occurrences.
def testFlakeHasUnreportedOccurrences(self):
flake = Flake.query().fetch()[0]
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=900)
flake_issue.last_updated_time_by_flake_detection = (
self._GetDatetimeHoursAgo(30))
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
for occurrence in occurrences:
occurrence.time_detected = self._GetDatetimeHoursAgo(10)
occurrence.put()
flakes_with_occurrences = flake_issue_util.GetFlakesWithEnoughOccurrences()
self.assertEqual(1, len(flakes_with_occurrences))
self.assertEqual(flake_issue, flakes_with_occurrences[0][2])
# This test tests that if the feature to create and update bugs is disabled,
# flakes will NOT be reported to Monorail.
@mock.patch.object(monorail_util, 'UpdateBug')
@mock.patch.object(monorail_util, 'CreateBug')
def testDisableCreateAndUpdateBugs(self, mock_create_bug_fn,
mock_update_bug_fn):
self.UpdateUnitTestConfigSettings('action_settings',
{'max_flake_bug_updates_per_day': 0})
flake = Flake.query().fetch()[0]
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
groups_wo_issue, groups_w_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs([(flake, occurrences,
None)]))
flake_issue_util.ReportFlakesToMonorail(groups_wo_issue, groups_w_issue)
self.assertFalse(mock_create_bug_fn.called)
self.assertFalse(mock_update_bug_fn.called)
# This test tests that when a flake has no flake issue attached, it creates
# a new issue and attach it to the flake.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'UpdateBug')
@mock.patch.object(monorail_util, 'CreateBug', return_value=66666)
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testCreateIssue(self, mock_issue, mock_create_bug_fn, mock_update_bug_fn,
_):
mock_issue.return_value = Issue({
'status': 'Untriaged',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '666666',
})
flake = Flake.query().fetch()[0]
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
groups_wo_issue, groups_w_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs([(flake, occurrences,
None)]))
flake_issue_util.ReportFlakesToMonorail(groups_wo_issue, groups_w_issue)
expected_status = 'Untriaged'
expected_summary = 'test_label is flaky'
expected_wrong_result_link = (
'https://bugs.chromium.org/p/chromium/issues/entry?status=Unconfirmed&'
'labels=Pri-1,Test-Findit-Wrong&components=Tools%3ETest%3EFindit%3E'
'Flakiness&summary=%5BFindit%5D%20Flake%20Detection%20-%20Wrong%20'
'result%3A%20test&comment=Link%20to%20flake%20details%3A%20'
'https://findit-for-me.appspot.com/flake/occurrences?key={}').format(
flake.key.urlsafe())
expected_description = textwrap.dedent("""
test_label is flaky.
Findit has detected 3 flake occurrences of this test within the
past 24 hours. List of all flake occurrences can be found at:
https://findit-for-me.appspot.com/flake/occurrences?key={}.
Unless the culprit CL is found and reverted, please disable this test first
within 30 minutes then find an appropriate owner.
If the result above is wrong, please file a bug using this link:
{}
Automatically posted by the findit-for-me app (https://goo.gl/Ot9f7N)."""
).format(flake.key.urlsafe(),
expected_wrong_result_link)
expected_labels = [
'Sheriff-Chromium', 'Type-Bug', 'Test-Flaky', 'Test-Findit-Detected',
'Pri-1'
]
self.assertTrue(mock_create_bug_fn.called)
self.assertFalse(mock_update_bug_fn.called)
issue = mock_create_bug_fn.call_args_list[0][0][0]
self.assertEqual(expected_status, issue.status)
self.assertEqual(expected_summary, issue.summary)
self.assertEqual(expected_description, issue.description)
self.assertEqual(expected_labels, issue.labels)
self.assertEqual(1, len(issue.field_values))
self.assertEqual('Flaky-Test', issue.field_values[0].to_dict()['fieldName'])
self.assertEqual('test', issue.field_values[0].to_dict()['fieldValue'])
fetched_flakes = Flake.query().fetch()
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertEqual(1, len(fetched_flakes))
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(66666, fetched_flake_issues[0].issue_id)
self.assertEqual(
datetime.datetime(2018, 1, 2),
fetched_flake_issues[0].last_updated_time_by_flake_detection)
self.assertEqual(fetched_flakes[0].flake_issue_key,
fetched_flake_issues[0].key)
# This test tests that when a flake has a flake issue attached and the issue
# is still open, it directly updates the issue.
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateBug')
@mock.patch.object(monorail_util, 'CreateBug')
def testUpdateIssue(self, mock_create_bug_fn, mock_update_bug_fn,
mock_get_merged_issue):
flake = Flake.query().fetch()[0]
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
mock_get_merged_issue.return_value.id = 12345
mock_get_merged_issue.return_value.open = True
groups_wo_issue, groups_w_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs([(flake, occurrences,
flake_issue)]))
flake_issue_util.ReportFlakesToMonorail(groups_wo_issue, groups_w_issue)
expected_wrong_result_link = (
'https://bugs.chromium.org/p/chromium/issues/entry?status=Unconfirmed&'
'labels=Pri-1,Test-Findit-Wrong&components=Tools%3ETest%3EFindit%3E'
'Flakiness&summary=%5BFindit%5D%20Flake%20Detection%20-%20Wrong%20'
'result%3A%20test&comment=Link%20to%20flake%20details%3A%20'
'https://findit-for-me.appspot.com/flake/occurrences?key={}').format(
flake.key.urlsafe())
sheriff_queue_message = (
'Since these tests are still flaky, this issue has been moved back onto'
' the Sheriff Bug Queue if it hasn\'t already.')
expected_comment = textwrap.dedent("""
test_label is flaky.
Findit has detected 3 new flake occurrences of this test. List
of all flake occurrences can be found at:
https://findit-for-me.appspot.com/flake/occurrences?key={}.
{}
If the result above is wrong, please file a bug using this link:
{}
Automatically posted by the findit-for-me app (https://goo.gl/Ot9f7N)."""
).format(flake.key.urlsafe(),
sheriff_queue_message,
expected_wrong_result_link)
self.assertFalse(mock_create_bug_fn.called)
self.assertTrue(mock_update_bug_fn)
comment = mock_update_bug_fn.call_args_list[0][0][1]
self.assertEqual(expected_comment, comment)
fetched_flakes = Flake.query().fetch()
fetched_flake_issues = FlakeIssue.query().fetch()
flake_issue = FlakeIssue.Get('chromium', 12345)
self.assertEqual(1, len(fetched_flakes))
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(12345, fetched_flake_issues[0].issue_id)
self.assertEqual(
datetime.datetime(2018, 1, 2),
flake_issue.last_updated_time_by_flake_detection)
self.assertEqual(fetched_flakes[0].flake_issue_key, flake_issue.key)
# This test tests that when a flake has a flake issue attached and the issue
# was merged to another open bug, it updates the destination bug with
# a previous tracking bug id.
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateBug')
@mock.patch.object(monorail_util, 'CreateBug')
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testUpdateIssueWithPreviousTrackingBugId(
self, mock_issue, mock_create_bug_fn, mock_update_bug_fn,
mock_get_merged_issue):
flake = Flake.query().fetch()[0]
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_issue.return_value = Issue({
'status': 'Available',
'labels': ['Type-Bug', 'Pri-1'],
'updated': '2018-12-07T17:52:45',
'id': '56789',
})
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
mock_get_merged_issue.return_value.id = 56789
mock_get_merged_issue.return_value.open = True
groups_wo_issue, groups_w_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs([(flake, occurrences,
flake_issue)]))
flake_issue_util.ReportFlakesToMonorail(groups_wo_issue, groups_w_issue)
expected_previous_bug_description = (
'\n\nThis flaky test was previously tracked in bug 12345.\n\n')
comment = mock_update_bug_fn.call_args_list[0][0][1]
self.assertIn(expected_previous_bug_description, comment)
self.assertFalse(mock_create_bug_fn.called)
self.assertTrue(mock_update_bug_fn.called)
# This test tests that when Flake Detection tries to create or update multiple
# bugs, if it encounters failures such as no permissions to update a specific
# bug, it shouldn't crash, instead, it should move on to next ones.
@mock.patch.object(monorail_util, 'IssueTrackerAPI')
def testReportFlakeToMonorailRecoversFromFailures(self,
mock_issue_tracker_api):
mock_issue_tracker_api.side_effect = HttpError(
mock.Mock(status=403), 'Error happened')
flake1 = Flake.query().fetch()[0]
occurrences1 = FlakeOccurrence.query(ancestor=flake1.key).filter(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
flake2 = self._CreateFlake('step_other', 'test_other', 'test_label_other')
self._CreateFlakeOccurrence(777, 'step1_other', 'test1_other', 54321,
flake2.key)
self._CreateFlakeOccurrence(888, 'step2_other', 'test2_other', 54322,
flake2.key)
self._CreateFlakeOccurrence(999, 'step3_other', 'test3_other', 54323,
flake2.key)
occurrences2 = FlakeOccurrence.query(ancestor=flake2.key).filter(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
groups_wo_issue, groups_w_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs(
[(flake1, occurrences1, None), (flake2, occurrences2, None)]))
flake_issue_util.ReportFlakesToMonorail(groups_wo_issue, groups_w_issue)
# This test tests that if the feature to report flakes to Flake Analyzer is
# disabled, flakes will NOT be reported.
@mock.patch.object(flake_issue_util, 'AnalyzeDetectedFlakeOccurrence')
def testDisableReportFlakesToFlakeAnalyzer(self,
mock_analyze_flake_occurrence):
self.UpdateUnitTestConfigSettings(
'flake_detection_settings', {'report_flakes_to_flake_analyzer': False})
flake = Flake.query().fetch()[0]
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
flake_issue_util.ReportFlakesToFlakeAnalyzer([(flake, occurrences, None)])
self.assertFalse(mock_analyze_flake_occurrence.called)
@mock.patch.object(flake_issue_util, 'AnalyzeDetectedFlakeOccurrence')
def testReportFlakesToFlakeAnalyzer(self, mock_analyze_flake_occurrence):
flake = Flake.query().fetch()[0]
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
occurrences = FlakeOccurrence.query(
ndb.OR(
FlakeOccurrence.flake_type == FlakeType.CQ_FALSE_REJECTION,
FlakeOccurrence.flake_type == FlakeType.RETRY_WITH_PATCH)).fetch()
flake_issue_util.ReportFlakesToFlakeAnalyzer([(flake, occurrences,
flake_issue)])
self.assertEqual(3, mock_analyze_flake_occurrence.call_count)
expected_call_args = []
for occurrence in occurrences:
expected_call_args.append(((flake, occurrence, 12345),))
self.assertEqual(expected_call_args,
mock_analyze_flake_occurrence.call_args_list)
# This test tests that an open issue related to flaky tests will NOT be found
# if it ONLY has the test name inside the summary.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryNotFound(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test'
issue.labels = []
issue.components = []
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
None, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will be found if
# it has the test name inside the summary and the 'Test-Flaky' label.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryWithFlakeLabel(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test'
issue.labels = ['Test-Flaky']
issue.components = []
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
123, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will be found if
# it has the test name inside the summary and the 'Tests>Flaky' component.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryWithFlakeComponent(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test'
issue.labels = []
issue.components = ['Tests>Flaky']
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
123, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will be found if
# it has the test name inside the summary and any of the flake keywords.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryWithKeywordFlake(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test is a flake'
issue.labels = []
issue.components = []
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
123, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will be found if
# it has the test name inside the summary and any of the flake keywords.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryWithKeywordFlaky(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test is flaky'
issue.labels = []
issue.components = []
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
123, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will be found if
# it has the test name inside the summary and any of the flake keywords.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryWithKeywordFlakiness(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test is causing flakiness'
issue.labels = []
issue.components = []
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
123, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will not be found
# if has the Test-FIndit-Wrong label.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInSummaryWithTestFinditWrongLabel(
self, mock_get_open_issues, mock_get_issue_id_by_customized_field):
issue = mock.Mock()
issue.summary = 'suite.test is causing flakiness'
issue.labels = ['Test-Findit-Wrong']
issue.components = []
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
None, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with('summary:suite.test is:open',
'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that an open issue related to flaky tests will be found if
# it has the test name inside the Flaky-Test customized field.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestBySummary',
return_value=None)
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testSearchOpenIssueFlakyTestInCustomizedField(
self, mock_get_open_issues, mock_get_issue_id_by_summary):
issue = mock.Mock()
issue.id = 123
mock_get_open_issues.return_value = [issue]
self.assertEqual(
123, flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test'))
mock_get_open_issues.assert_called_once_with(
'Flaky-Test=suite.test is:open', 'chromium')
mock_get_issue_id_by_summary.assert_called_once_with(
'suite.test', 'chromium')
# This test tests that the util first searches for open bugs by summary on
# Monorail and if it is found, then skip searching for customized field.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestBySummary',
return_value=12345)
def testSearchAndFoundOpenIssueBySummary(
self, mock_get_issue_id_by_summary,
mock_get_issue_id_by_customized_field):
self.assertEqual(
12345,
flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test',
'chromium'))
mock_get_issue_id_by_summary.assert_called_once_with(
'suite.test', 'chromium')
mock_get_issue_id_by_customized_field.assert_not_called()
# This test tests that the util first searches for open bugs on Monorail and
# if it is not found, then searches for customized field.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=12345)
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestBySummary',
return_value=None)
def testSearchAndFoundOpenIssueByCustomizedField(
self, mock_get_issue_id_by_summary,
mock_get_issue_id_by_customized_field):
self.assertEqual(
12345,
flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test',
'chromium'))
mock_get_issue_id_by_summary.assert_called_once_with(
'suite.test', 'chromium')
mock_get_issue_id_by_customized_field.assert_called_once_with(
'suite.test', 'chromium')
# This test tests that the util first searches for open bugs on Monorail and
# if it is not found, then searches for customized field, and if still not
# found, returns None.
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestByCustomizedField',
return_value=None)
@mock.patch.object(
flake_issue_util,
'_GetOpenIssueIdForFlakyTestBySummary',
return_value=None)
def testSearchAndNotFoundOpenIssue(self, mock_get_issue_id_by_summary,
mock_get_issue_id_by_customized_field):
self.assertEqual(
None,
flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test',
'chromium'))
mock_get_issue_id_by_summary.assert_called_once_with(
'suite.test', 'chromium')
mock_get_issue_id_by_customized_field.assert_called_once_with(
'suite.test', 'chromium')
# This test tests that when there are multiple issues related to the flaky
# test, returns the id of the issue that was filed earliest.
@mock.patch.object(monorail_util, 'GetOpenIssues')
def testGetExistingOpenBugIdForTestReturnsEarliestBug(self,
mock_get_open_issues):
issue1 = mock.Mock()
issue1.id = 456
issue1.summary = 'suite.test is flaky'
issue1.labels = []
issue1.components = []
issue2 = mock.Mock()
issue2.id = 123
issue2.summary = 'suite.test is flaky'
issue2.labels = []
issue2.components = []
mock_get_open_issues.return_value = [issue1, issue2]
self.assertEqual(
123,
flake_issue_util.SearchOpenIssueIdForFlakyTest('suite.test',
'chromium'))
# This test tests that if a flake has a flake issue attached and the bug is
# open (not merged) on Monorail, then should directly update that bug.
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'CreateIssueWithIssueGenerator')
def testHasFlakeAndFlakeIssueAndBugIsOpen(
self, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, mock_get_merged_issue):
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_get_merged_issue.return_value.id = 12345
mock_get_merged_issue.return_value.open = True
test_issue_generator = TestIssueGenerator()
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
self.assertFalse(mock_create_with_issue_generator_fn.called)
mock_update_with_issue_generator_fn.assert_called_once_with(
issue_id=12345, issue_generator=test_issue_generator)
fetched_flakes = Flake.query().fetch()
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertIn(flake, fetched_flakes)
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(12345, fetched_flake_issues[0].issue_id)
self.assertEqual(fetched_flakes[0].flake_issue_key,
fetched_flake_issues[0].key)
# This test tests that if a flake has a flake issue attached and the bug is
# closed (not merged) on Monorail, then should create a new one if an existing
# open bug is not found on Monorail.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(
monorail_util, 'CreateIssueWithIssueGenerator', return_value=66666)
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasFlakeAndFlakeIssueAndBugIsClosed(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, mock_get_merged_issue, _):
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_issue.return_value = Issue({
'status': 'Available',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '56789',
})
mock_get_merged_issue.return_value.id = 12345
mock_get_merged_issue.return_value.open = False
test_issue_generator = TestIssueGenerator()
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
mock_create_with_issue_generator_fn.assert_called_once_with(
issue_generator=test_issue_generator)
self.assertFalse(mock_update_with_issue_generator_fn.called)
fetched_flakes = Flake.query().fetch()
new_flake_issue = FlakeIssue.Get(
monorail_project='chromium', issue_id=66666)
self.assertEqual(fetched_flakes[0].flake_issue_key, new_flake_issue.key)
@mock.patch.object(
FlakeAnalysisIssueGenerator, 'GetAutoAssignOwner', return_value=None)
@mock.patch.object(
MasterFlakeAnalysis,
'GetRepresentativeSwarmingTaskId',
return_value='task_id')
@mock.patch.object(Flake, 'NormalizeStepName', return_value='normalized_step')
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'UpdateBug')
@mock.patch.object(monorail_util, 'CreateBug', return_value=66666)
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testCreateIssueWhenFlakeAndIssueDoesNotExist(
self, mock_issue, mock_create_bug_fn, mock_update_bug_fn, *_):
master_name = 'm'
builder_name = 'b'
build_number = 100
step_name = 's'
test_name = 't'
culprit = FlakeCulprit.Create('git', 'rev', 1)
culprit.put()
analysis = MasterFlakeAnalysis.Create(master_name, builder_name,
build_number, step_name, test_name)
analysis.original_master_name = master_name
analysis.original_builder_name = builder_name
analysis.original_build_number = build_number
analysis.data_points = [
DataPoint.Create(commit_position=200, pass_rate=.5, git_hash='hash')
]
analysis.culprit_urlsafe_key = culprit.key.urlsafe()
analysis.confidence_in_culprit = .5
analysis.Save()
mock_issue.return_value = Issue({
'status': 'Untriaged',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '66666',
})
issue_generator = FlakeAnalysisIssueGenerator(analysis)
description = issue_generator.GetDescription()
self.assertEqual(66666,
flake_issue_util.CreateOrUpdateIssue(issue_generator))
self.assertTrue(mock_create_bug_fn.called)
self.assertFalse(mock_update_bug_fn.called)
self.assertIn('Test-Findit-Wrong', description)
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(66666, fetched_flake_issues[0].issue_id)
self.assertEqual(
None, fetched_flake_issues[0].last_updated_time_by_flake_detection)
@mock.patch.object(
MasterFlakeAnalysis,
'GetRepresentativeSwarmingTaskId',
return_value='task_id')
@mock.patch.object(Flake, 'NormalizeTestName', return_value='normalized_test')
@mock.patch.object(Flake, 'NormalizeStepName', return_value='normalized_step')
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateBug')
@mock.patch.object(monorail_util, 'CreateBug')
def testUpdateIssueWhenFlakeAndIssueExists(
self, mock_create_bug_fn, mock_update_bug_fn, mock_get_merged_issue, *_):
master_name = 'm'
builder_name = 'b'
build_number = 100
step_name = 's'
test_name = 't'
analysis = MasterFlakeAnalysis.Create(master_name, builder_name,
build_number, step_name, test_name)
analysis.original_master_name = master_name
analysis.original_builder_name = builder_name
analysis.original_build_number = build_number
analysis.Save()
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='normalized_step',
normalized_test_name='normalized_test',
test_label_name='test_label')
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_get_merged_issue.return_value.id = 12345
mock_get_merged_issue.return_value.open = True
issue_generator = FlakeAnalysisIssueGenerator(analysis)
flake_issue_util.CreateOrUpdateIssue(issue_generator)
self.assertFalse(mock_create_bug_fn.called)
self.assertTrue(mock_update_bug_fn.called)
fetched_flakes = Flake.query().fetch()
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertIn(flake, fetched_flakes)
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(12345, fetched_flake_issues[0].issue_id)
self.assertEqual(
None, fetched_flake_issues[0].last_updated_time_by_flake_detection)
self.assertEqual(fetched_flakes[0].flake_issue_key,
fetched_flake_issues[0].key)
# This test tests that if there is no flake for a test, and found an existing
# issue about this flaky test on Monorail, then should create a new flake,
# update the issue and attach the issue to the flake.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=99999)
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'CreateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasNoFlakeAndFoundAnExistingOpenIssue(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, _):
step_name = 'step_2'
test_name = 'test_2'
test_issue_generator = TestIssueGenerator(
step_name=step_name, test_name=test_name)
mock_issue.return_value = Issue({
'status': 'Available',
'priority': 1,
'updated': '2018-12-07T17:52:45',
})
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
self.assertFalse(mock_create_with_issue_generator_fn.called)
mock_update_with_issue_generator_fn.assert_called_once_with(
issue_id=99999, issue_generator=test_issue_generator)
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(99999, fetched_flake_issues[0].issue_id)
# This test tests that if a flake has a flake issue attached and the bug is
# closed (not merged) on Monorail, but found an existing open issue on
# Monorail, then should update the issue and attach it to the flake.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=99999)
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'CreateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasFlakeAndFlakeAndBugIsClosedButFoundAnExistingOpenIssue(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, mock_get_merged_issue, _):
mock_issue.return_value = Issue({
'status': 'Available',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '99999',
})
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_get_merged_issue.return_value.id = 12345
mock_get_merged_issue.return_value.open = False
test_issue_generator = TestIssueGenerator()
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
self.assertFalse(mock_create_with_issue_generator_fn.called)
mock_update_with_issue_generator_fn.assert_called_once_with(
issue_id=99999, issue_generator=test_issue_generator)
fetched_flakes = Flake.query().fetch()
self.assertEqual('step', fetched_flakes[0].normalized_step_name)
self.assertEqual('suite.test', fetched_flakes[0].normalized_test_name)
original_issues = FlakeIssue.query(FlakeIssue.issue_id == 12345).fetch()
self.assertEqual(1, len(original_issues))
new_flake_issue = FlakeIssue.Get(
monorail_project='chromium', issue_id=99999)
self.assertEqual(fetched_flakes[0].flake_issue_key, new_flake_issue.key)
# This test tests that if a flake has a flake issue attached and the bug was
# merged to another bug on Monorail, but that destination bug was closed, then
# should create a new one if an existing open bug is not found on Monorail.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(
monorail_util, 'CreateIssueWithIssueGenerator', return_value=66666)
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasFlakeAndFlakeIssueAndBugWasMergedToAClosedBug(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, mock_get_merged_issue, _):
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_issue.return_value = Issue({
'status': 'Available',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '56789',
})
# Merged issue is already in data store.
FlakeIssue.Create(monorail_project='chromium', issue_id=56789).put()
mock_get_merged_issue.return_value.id = 56789
mock_get_merged_issue.return_value.open = False
test_issue_generator = TestIssueGenerator()
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
mock_create_with_issue_generator_fn.assert_called_once_with(
issue_generator=test_issue_generator)
self.assertFalse(mock_update_with_issue_generator_fn.called)
fetched_flakes = Flake.query().fetch()
original_issue = FlakeIssue.Get(monorail_project='chromium', issue_id=12345)
merged_issue = FlakeIssue.Get(monorail_project='chromium', issue_id=56789)
self.assertEqual(original_issue.merge_destination_key, merged_issue.key)
new_flake_issue = FlakeIssue.Get(
monorail_project='chromium', issue_id=66666)
self.assertEqual(fetched_flakes[0].flake_issue_key, new_flake_issue.key)
# This test tests that if there is no existing flake for a test, and couldn't
# find an existing issue about this flaky test on Monorail, then should create
# a new flake and a new issue and attach the issue to the flake.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(
monorail_util, 'CreateIssueWithIssueGenerator', return_value=66666)
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasNoFlakeAndNoExistingOpenIssue(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, _):
step_name = 'step_1'
test_name = 'test_1'
mock_issue.return_value = Issue({
'status': 'Available',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '66666',
})
test_issue_generator = TestIssueGenerator(
step_name=step_name, test_name=test_name)
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
mock_create_with_issue_generator_fn.assert_called_once_with(
issue_generator=test_issue_generator)
self.assertFalse(mock_update_with_issue_generator_fn.called)
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(66666, fetched_flake_issues[0].issue_id)
# This test tests that if there is a flake for a test, but no flake issue
# attached and couldn't find an existing issue about this flaky test on
# Monorail, then should create a new issue and attach the issue to the flake.
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=None)
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(
monorail_util, 'CreateIssueWithIssueGenerator', return_value=66666)
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasFlakeButNoFlakeIssueAndNoExistingOpenIssue(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, _):
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake.put()
mock_issue.return_value = Issue({
'status': 'Available',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '66666',
})
test_issue_generator = TestIssueGenerator()
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
mock_create_with_issue_generator_fn.assert_called_once_with(
issue_generator=test_issue_generator)
self.assertFalse(mock_update_with_issue_generator_fn.called)
fetched_flakes = Flake.query().fetch()
fetched_flake_issues = FlakeIssue.query().fetch()
self.assertIn(flake, fetched_flakes)
self.assertEqual('step', fetched_flakes[0].normalized_step_name)
self.assertEqual('suite.test', fetched_flakes[0].normalized_test_name)
self.assertEqual(1, len(fetched_flake_issues))
self.assertEqual(66666, fetched_flake_issues[0].issue_id)
self.assertEqual(flake.flake_issue_key, fetched_flake_issues[0].key)
# This test tests that if a flake has a flake issue attached and the bug was
# merged to another bug on Monorail, and that destination bug is open, then
# should update the destination bug.
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'CreateIssueWithIssueGenerator')
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testHasFlakeAndFlakeIssueAndBugWasMergedToAnOpenBug(
self, mock_issue, mock_create_with_issue_generator_fn,
mock_update_with_issue_generator_fn, mock_get_merged_issue):
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake.flake_issue_key = flake_issue.key
flake.put()
mock_issue.return_value = Issue({
'status': 'Available',
'labels': ['Type-Bug', 'Pri-1'],
'updated': '2018-12-07T17:52:45',
'id': '45678',
})
leaf_flake_issue = FlakeIssue.Create(
monorail_project='chromium', issue_id=67890)
leaf_flake_issue.merge_destination_key = flake_issue.key
leaf_flake_issue.put()
mock_get_merged_issue.return_value.id = 45678
mock_get_merged_issue.return_value.open = True
test_issue_generator = TestIssueGenerator()
flake_issue_util.CreateOrUpdateIssue(test_issue_generator)
self.assertFalse(mock_create_with_issue_generator_fn.called)
mock_update_with_issue_generator_fn.assert_called_once_with(
issue_id=45678, issue_generator=test_issue_generator)
fetched_flakes = Flake.query().fetch()
original_issue = FlakeIssue.Get(monorail_project='chromium', issue_id=12345)
merged_issue = FlakeIssue.Get(monorail_project='chromium', issue_id=45678)
self.assertEqual(original_issue.merge_destination_key, merged_issue.key)
self.assertEqual(fetched_flakes[0].flake_issue_key, original_issue.key)
@mock.patch.object(
flake_issue_util, 'SearchOpenIssueIdForFlakyTest', return_value=True)
def testOpenIssueAlreadyExistsForFlakyTest(self, _):
self.assertTrue(flake_issue_util.OpenIssueAlreadyExistsForFlakyTest('t'))
def testGetFlakeIssueDataInconsistent(self):
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=12345)
flake_issue.put()
flake_issue_key = flake_issue.key
flake = Flake.Create(
luci_project='chromium',
normalized_step_name='step',
normalized_test_name='suite.test',
test_label_name='*/suite.test/*')
flake.flake_issue_key = flake_issue_key
flake.put()
flake_issue_key.delete()
self.assertIsNone(flake_issue_util.GetFlakeIssue(flake))
@mock.patch.object(flake_issue_util, 'GetAndUpdateMergedIssue')
def testGetFlakeGroupsForActionsOnBugs(self, mock_get_merged_issue):
# New flake, no linked issue.
flake1 = self._CreateFlake('s1', 'suite1.t1', 'suite1.t1', 'suite1')
occurrences1 = [
self._CreateFlakeOccurrence(1, 's1', 'suite1.t1', 12345, flake1.key),
self._CreateFlakeOccurrence(2, 's1', 'suite1.t1', 12346, flake1.key)
]
# New flake, no linked issue, same group as flake1.
flake2 = self._CreateFlake('s1', 'suite1.t2', 'suite1.t2', 'suite1')
occurrences2 = [
self._CreateFlakeOccurrence(1, 's1', 'suite1.t2', 12345, flake2.key),
self._CreateFlakeOccurrence(2, 's1', 'suite1.t2', 12346, flake2.key)
]
# New flake, no linked issue, same step and suite as flake1&2,
# but different occurrences.
flake3 = self._CreateFlake('s3', 'suite1.t3', 'suite1.t3', 'suite1')
occurrences3 = [
self._CreateFlakeOccurrence(1, 's1', 'suite1.t3', 12345, flake3.key),
self._CreateFlakeOccurrence(3, 's1', 'suite1.t3', 12347, flake3.key)
]
# New flake, no linked issue, different step and suite.
flake4 = self._CreateFlake('s2', 'suite2.t4', 'suite2.t4', 'suite2')
occurrences4 = [
self._CreateFlakeOccurrence(1, 's2', 'suite2.t4', 12345, flake4.key),
self._CreateFlakeOccurrence(2, 's2', 'suite2.t4', 12346, flake4.key)
]
# Old flake, with issue.
flake_issue1 = FlakeIssue.Create(
monorail_project='chromium', issue_id=56789)
flake_issue1.put()
flake5 = self._CreateFlake('s2', 'suite2.t5', 'suite2.t5')
flake5.flake_issue_key = flake_issue1.key
flake5.put()
occurrences5 = [
self._CreateFlakeOccurrence(1, 's2', 'suite2.t4', 12345, flake5.key),
self._CreateFlakeOccurrence(2, 's2', 'suite2.t4', 12346, flake5.key)
]
# Old flake, with same issue as flake5.
flake6 = self._CreateFlake('s3', 'suite3.t6', 'suite3.t6')
flake6.flake_issue_key = flake_issue1.key
flake6.put()
occurrences6 = [
self._CreateFlakeOccurrence(3, 's3', 'suite3.t6', 34467, flake6.key),
self._CreateFlakeOccurrence(4, 's3', 'suite3.t6', 35546, flake6.key),
self._CreateFlakeOccurrence(5, 's3', 'suite3.t6', 67543, flake6.key)
]
# Old flake, different issue.
flake_issue2 = FlakeIssue.Create(
monorail_project='chromium', issue_id=67890)
flake_issue2.put()
flake7 = self._CreateFlake('s3', 'suite3.t7', 'suite3.t7')
flake7.flake_issue_key = flake_issue2.key
flake7.put()
occurrences7 = [
self._CreateFlakeOccurrence(1, 's3', 'suite3.t7', 12345, flake7.key),
self._CreateFlakeOccurrence(2, 's3', 'suite3.t7', 12346, flake7.key)
]
flake_group1 = flake_issue_util.FlakeGroupByOccurrences(
flake1, occurrences1)
flake_group1.AddFlakeIfBelong(flake2, occurrences2)
flake_group2 = flake_issue_util.FlakeGroupByOccurrences(
flake3, occurrences3)
flake_group3 = flake_issue_util.FlakeGroupByOccurrences(
flake4, occurrences4)
flake_group4 = flake_issue_util.FlakeGroupByFlakeIssue(
flake_issue1, flake5, occurrences5, flake_issue1)
flake_group4.AddFlakeIfBelong(flake6, occurrences6)
flake_group5 = flake_issue_util.FlakeGroupByFlakeIssue(
flake_issue2, flake7, occurrences7, flake_issue2)
flake_tuples_to_report = [(flake1, occurrences1, None),
(flake2, occurrences2, None),
(flake3, occurrences3, None),
(flake4, occurrences4, None),
(flake5, occurrences5, flake_issue1),
(flake6, occurrences6, flake_issue1),
(flake7, occurrences7, flake_issue2)]
mock_get_merged_issue.return_value.open = True
flake_groups_without_issue, flake_groups_with_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs(flake_tuples_to_report))
self.assertItemsEqual(
[flake_group1.ToDict(),
flake_group2.ToDict(),
flake_group3.ToDict()],
[group.ToDict() for group in flake_groups_without_issue])
self.assertItemsEqual(
[flake_group4.ToDict(), flake_group5.ToDict()],
[group.ToDict() for group in flake_groups_with_issue])
# This test is for when the issue linked to flakes is closed, should not
# group flakes by issue in this case.
@mock.patch.object(flake_issue_util, 'GetAndUpdateMergedIssue')
def testGetFlakeGroupsForActionsOnBugsIssueClosed(self,
mock_get_merged_issue):
mock_get_merged_issue.return_value.open = False
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=56789)
flake_issue.put()
flake = self._CreateFlake('s2', 'suite2.t5', 'suite2.t5')
flake.flake_issue_key = flake_issue.key
flake.put()
occurrences = [
self._CreateFlakeOccurrence(1, 's2', 'suite2.t4', 12345, flake.key),
self._CreateFlakeOccurrence(2, 's2', 'suite2.t4', 12346, flake.key)
]
flake_group = flake_issue_util.FlakeGroupByOccurrences(flake, occurrences)
flake_groups_without_issue, flake_groups_with_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs([(flake, occurrences,
flake_issue)]))
self.assertEqual([flake_group.ToDict()],
[group.ToDict() for group in flake_groups_without_issue])
self.assertEqual([], flake_groups_with_issue)
# This test is for when the issue linked to flakes is merged to an open bug.
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
def testGetFlakeGroupsForActionsOnBugsIssueNewMergeIssue(
self, mock_get_merged_issue):
mock_get_merged_issue.return_value.open = True
mock_get_merged_issue.return_value.id = 54321
flake_issue1 = FlakeIssue.Create(
monorail_project='chromium', issue_id=56789)
flake_issue1.put()
flake_issue2 = FlakeIssue.Create(
monorail_project='chromium', issue_id=54321)
flake_issue2.put()
flake = self._CreateFlake('s2', 'suite2.t5', 'suite2.t5')
flake.flake_issue_key = flake_issue1.key
flake.put()
occurrences = [
self._CreateFlakeOccurrence(1, 's2', 'suite2.t4', 12345, flake.key),
self._CreateFlakeOccurrence(2, 's2', 'suite2.t4', 12346, flake.key)
]
flake_group = flake_issue_util.FlakeGroupByFlakeIssue(
flake_issue2, flake, occurrences, flake_issue1)
flake_groups_without_issue, flake_groups_with_issue = (
flake_issue_util.GetFlakeGroupsForActionsOnBugs([(flake, occurrences,
flake_issue1)]))
self.assertEqual([], flake_groups_without_issue)
self.assertEqual([flake_group.ToDict()],
[group.ToDict() for group in flake_groups_with_issue])
@mock.patch.object(
time_util, 'GetUTCNow', return_value=datetime.datetime(2018, 1, 2))
@mock.patch.object(
monorail_util, 'CreateIssueWithIssueGenerator', return_value=234567)
@mock.patch.object(
monorail_util,
'GetMonorailIssueForIssueId',
return_value=Issue({
'status': 'Untriaged',
'priority': 1,
'updated': '2018-12-07T17:52:45',
'id': '234567',
}))
def testCreateIssueForAGroup(self, *_):
flake1 = self._CreateFlake('s1', 'suite1.t1', 'suite1.t1')
occurrences1 = [
self._CreateFlakeOccurrence(1, 's1', 'suite1.t1', 12345, flake1.key),
self._CreateFlakeOccurrence(2, 's1', 'suite1.t1', 12346, flake1.key)
]
flake2 = self._CreateFlake('s1', 'suite1.t2', 'suite1.t2')
occurrences2 = [
self._CreateFlakeOccurrence(1, 's1', 'suite1.t2', 12345, flake2.key),
self._CreateFlakeOccurrence(2, 's1', 'suite1.t2', 12346, flake2.key)
]
flake_group = flake_issue_util.FlakeGroupByOccurrences(flake1, occurrences1)
flake_group.AddFlakeIfBelong(flake2, occurrences2)
flake_issue_util._CreateIssuesForFlakes([flake_group])
flake_issue = flake_issue_util.GetFlakeIssue(flake1)
self.assertEqual(234567, flake_issue.issue_id)
self.assertEqual(
datetime.datetime(2018, 1, 2),
flake_issue.last_updated_time_by_flake_detection)
@mock.patch.object(
time_util, 'GetUTCNow', return_value=datetime.datetime(2018, 1, 2))
@mock.patch.object(monorail_util, 'UpdateIssueWithIssueGenerator')
def testUpdateIssueForAGroup(self, *_):
flake_issue = FlakeIssue.Create(monorail_project='chromium', issue_id=56789)
flake_issue.put()
flake5 = self._CreateFlake('s2', 'suite2.t5', 'suite2.t5')
flake5.flake_issue_key = flake_issue.key
flake5.put()
occurrences5 = [
self._CreateFlakeOccurrence(1, 's2', 'suite2.t4', 12345, flake5.key),
self._CreateFlakeOccurrence(2, 's2', 'suite2.t4', 12346, flake5.key)
]
# Old flake, with same issue as flake5.
flake6 = self._CreateFlake('s3', 'suite3.t6', 'suite3.t6')
flake6.flake_issue_key = flake_issue.key
flake6.put()
occurrences6 = [
self._CreateFlakeOccurrence(3, 's3', 'suite3.t6', 34467, flake6.key),
self._CreateFlakeOccurrence(4, 's3', 'suite3.t6', 35546, flake6.key),
self._CreateFlakeOccurrence(5, 's3', 'suite3.t6', 67543, flake6.key)
]
flake_group = flake_issue_util.FlakeGroupByFlakeIssue(
flake_issue, flake5, occurrences5, flake_issue)
flake_group.AddFlakeIfBelong(flake6, occurrences6)
flake_issue_util._UpdateIssuesForFlakes([flake_group])
flake_issue = flake_issue_util.GetFlakeIssue(flake6)
self.assertEqual(
datetime.datetime(2018, 1, 2),
flake_issue.last_updated_time_by_flake_detection)
def testUpdateFlakeIssueWithMonorailIssue(self):
flake_issue = FlakeIssue.Create('chromium', 1)
flake_issue.put()
monorail_issue = Issue({
'id': '1',
'labels': ['Type-Bug', 'Pri-2'],
'status': 'Assigned',
'updated': '2018-12-10T0:0:0',
})
flake_issue_util._UpdateFlakeIssueWithMonorailIssue(flake_issue,
monorail_issue)
self.assertEqual('Assigned', flake_issue.status)
self.assertEqual(
datetime.datetime(2018, 12, 10),
flake_issue.last_updated_time_in_monorail)
self.assertEqual(['Type-Bug', 'Pri-2'], flake_issue.labels)
def testGetOrCreateFlakeIssueExisting(self):
flake_issue = FlakeIssue.Create('chromium', 1)
flake_issue.put()
self.assertEqual(flake_issue,
flake_issue_util._GetOrCreateFlakeIssue(1, 'chromium'))
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
def testGetOrCreateFlakeIssueNewIssue(self, mocked_issue):
mocked_issue.return_value = Issue({
'id': '2',
'labels': ['Type-Bug', 'Pri-2'],
'status': 'Assigned',
'updated': '2018-12-10T0:0:0',
})
expected_flake_issue = FlakeIssue.Create('chromium', 2)
expected_flake_issue.status = 'Assigned'
expected_flake_issue.last_updated_time_in_monorail = datetime.datetime(
2018, 12, 10)
expected_flake_issue.labels = ['Type-Bug', 'Pri-2']
self.assertEqual(expected_flake_issue,
flake_issue_util._GetOrCreateFlakeIssue(2, 'chromium'))
def testUpdateIssueLeaves(self):
final_issue = FlakeIssue.Create('chromium', 3)
final_issue.put()
obsolete_merged_issue = FlakeIssue.Create('chromium', 2)
obsolete_merged_issue.put()
flake_issue = FlakeIssue.Create('chromium', 1)
flake_issue.merge_destination_key = obsolete_merged_issue.key
flake_issue.put()
flake_issue_util.UpdateIssueLeaves(obsolete_merged_issue.key,
final_issue.key)
flake_issue = flake_issue.key.get()
self.assertEqual(flake_issue.merge_destination_key, final_issue.key)
def testGetIssueStatusesNeedingUpdating(self):
self.assertEqual([
None, 'Assigned', 'Available', 'ExternalDependency'
'Started', 'Unconfirmed', 'Untriaged'
], flake_issue_util._GetIssueStatusesNeedingUpdating())
def testGetFlakeIssuesNeedingUpdating(self):
open_flake_issue = FlakeIssue.Create('chromium', 1)
open_flake_issue.status = 'Assigned'
open_flake_issue.put()
closed_flake_issue = FlakeIssue.Create('chromium', 2)
closed_flake_issue.status = 'Verified'
closed_flake_issue.put()
self.assertEqual([open_flake_issue],
flake_issue_util._GetFlakeIssuesNeedingUpdating())
@mock.patch.object(monorail_util, 'GetMonorailIssueForIssueId')
@mock.patch.object(monorail_util, 'GetMergedDestinationIssueForId')
def testSyncOpenFlakeIssuesWithMonorail(self, mocked_merged_issue,
mocked_issue):
# Scenario:
# 1. FlakeIssue 1 is a duplicate of FlakeIssue 2, and already has
# |merge_destination_key| pointing to 2.
# 2. FlakeIssue 2 status is 'Available'.
# 3. Bug 2 in monorail was manually merged into bug 3, which is to be
# detected and updated accordingly.
#
# Expected result:
# 1. FlakeIssue 3 is created and set to 'Assigned'.
# 2. FlakeIssue 2's status is set to 'Duplicate'.
# 3. FlakeIssue 2's |merge_destination_key| is set to FlakeIssue3's key.
# 4. FlakeIssue 1's |merge_destination_key| is set to FlakeIssue3's key.
# |flake_issue_2| is what's active according to Findit, but is out of date
# with Monorail.
flake_issue_2 = FlakeIssue.Create('chromium', 2)
flake_issue_2.status = 'Available'
flake_issue_2.put()
# |flake_issue_1| was previously updated to be a duplicate of
# |flake_issue_2|, but that too is now outdated.
flake_issue_1 = FlakeIssue.Create('chromium', 1)
flake_issue_1.status = 'Duplicate'
flake_issue_1.merge_destination_key = flake_issue_2.key
flake_issue_1.put()
expected_labels = ['Type-Bug', 'Pri-2']
expected_last_updated_time = datetime.datetime(2018, 12, 10)
monorail_issue_2 = Issue({
'id': '2',
'labels': expected_labels,
'merged_into': '3',
'status': 'Duplicate',
'updated': '2018-12-10T0:0:0',
})
monorail_issue_3 = Issue({
'id': '3',
'labels': expected_labels,
'status': 'Assigned',
'updated': '2018-12-10T0:0:0',
})
def MockedGetMonorailIssueForIssueId(issue_id, _):
if issue_id == 2:
return monorail_issue_2
return monorail_issue_3
mocked_issue.side_effect = MockedGetMonorailIssueForIssueId
mocked_merged_issue.return_value = monorail_issue_3
flake_issue_util.SyncOpenFlakeIssuesWithMonorail()
flake_issue_1 = flake_issue_1.key.get()
flake_issue_2 = flake_issue_2.key.get()
flake_issue_3 = FlakeIssue.Get('chromium', 3)
self.assertEqual(flake_issue_1.merge_destination_key, flake_issue_3.key)
self.assertEqual(expected_last_updated_time,
flake_issue_2.last_updated_time_in_monorail)
self.assertEqual('Duplicate', flake_issue_2.status)
self.assertEqual(expected_labels, flake_issue_2.labels)
self.assertEqual(flake_issue_3.key, flake_issue_2.merge_destination_key)
self.assertIsNotNone(flake_issue_3)
self.assertEqual('Assigned', flake_issue_3.status)
self.assertEqual(expected_labels, flake_issue_3.labels)
self.assertEqual(expected_last_updated_time,
flake_issue_3.last_updated_time_in_monorail)
@mock.patch.object(
time_util, 'GetUTCNow', return_value=datetime.datetime(2018, 12, 20))
def testGetRemainingDailyUpdatesCount(self, _):
self.UpdateUnitTestConfigSettings('action_settings',
{'max_flake_bug_updates_per_day': 5})
flake_issue_1 = FlakeIssue.Create('chromium', 12345)
flake_issue_1.last_updated_time_by_flake_detection = datetime.datetime(
2018, 12, 19, 12, 0, 0) # Updated within 24 hours.
flake_issue_1.put()
flake_issue_2 = FlakeIssue.Create('chromium', 12346)
flake_issue_2.last_updated_time_with_analysis_results = datetime.datetime(
2018, 12, 19, 12, 1, 0) # Already commented with analysis results.
flake_issue_2.put()
flake_issue_3 = FlakeIssue.Create('chromium', 12347)
flake_issue_3.last_updated_time_by_flake_detection = datetime.datetime(
2018, 12, 18, 12, 0, 0) # Over 24 hours old.
flake_issue_3.put()
self.assertEqual(3, flake_issue_util.GetRemainingDailyUpdatesCount())