text: # Copyright 2019 Google LLC
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #      http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  """Tests for cleanup task."""
  # pylint: disable=protected-access
  
  from builtins import range
  from builtins import str
  import datetime
  import unittest
  
  import six
  
  from datastore import data_types
  from handlers.cron import cleanup
  from libs.issue_management import issue_tracker_policy
  from tests.test_libs import appengine_test_utils
  from tests.test_libs import helpers
  from tests.test_libs import test_utils
  
  ISSUE_IGNORE_LABEL = 'ClusterFuzz-Ignore'
  ISSUE_INVALID_FUZZER_LABEL = 'ClusterFuzz-Invalid-Fuzzer'
  ISSUE_MISTRIAGED_LABEL = 'ClusterFuzz-Wrong'
  ISSUE_NEEDS_FEEDBACK_LABEL = 'Needs-Feedback'
  ISSUE_VERIFIED_LABEL = 'ClusterFuzz-Verified'
  ISSUE_FUZZ_BLOCKER_LABEL = 'Fuzz-Blocker'
  
  
  @test_utils.with_cloud_emulators('datastore')
  class GetPredatorResultItemTest(unittest.TestCase):
    """Tests for the get_predator_result_item helper function."""
  
    def test_with_components(self):
      """Ensure that we return the components for test cases which have them."""
      result_with_component = {'result': {'suspected_components': ['A', 'B>C']}}
  
      testcase = test_utils.create_generic_testcase()
      testcase.set_metadata('predator_result', result_with_component)
  
      actual = cleanup._get_predator_result_item(testcase, 'suspected_components')
      self.assertListEqual(actual, ['A', 'B>C'])
  
    def test_no_components(self):
      """Ensure that we handle cases with a result, but no components field."""
      result_no_component = {'result': {}}
  
      testcase = test_utils.create_generic_testcase()
      testcase.set_metadata('predator_result', result_no_component)
  
      actual = cleanup._get_predator_result_item(testcase, 'suspected_components')
      self.assertIsNone(actual)
  
    def test_no_result(self):
      """Ensure that we handle cases without a predator result."""
      testcase = test_utils.create_generic_testcase()
      testcase.delete_metadata('predator_result')
  
      actual = cleanup._get_predator_result_item(
          testcase, 'suspected_components', default=[])
      self.assertListEqual(actual, [])
  
  
  @test_utils.with_cloud_emulators('datastore')
  class CleanupTest(unittest.TestCase):
    """Tests for various cleanup functions."""
  
    def setUp(self):
      helpers.patch(self, [
          'base.utils.utcnow',
          'handlers.cron.cleanup.get_crash_occurrence_platforms',
      ])
      self.mock.utcnow.return_value = test_utils.CURRENT_TIME
      self.issue = appengine_test_utils.create_generic_issue()
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_mark_duplicate_testcase_as_closed_with_no_issue_1(self):
      """Ensure that a regular bug older than 7 days does not get closed."""
      testcase = test_utils.create_generic_testcase(
          created_days_ago=data_types.DUPLICATE_TESTCASE_NO_BUG_DEADLINE + 1)
      testcase.status = 'Processed'
      testcase.put()
      cleanup.mark_duplicate_testcase_as_closed_with_no_issue(testcase=testcase)
      self.assertTrue(testcase.open)
  
    def test_mark_duplicate_testcase_as_closed_with_no_issue_2(self):
      """Ensure that a duplicate bug older than 7 days, with an associated
      issue does not get closed."""
      testcase = test_utils.create_generic_testcase(
          created_days_ago=data_types.DUPLICATE_TESTCASE_NO_BUG_DEADLINE + 1)
      testcase.bug_information = str(self.issue.id)
      testcase.status = 'Duplicate'
      testcase.put()
      cleanup.mark_duplicate_testcase_as_closed_with_no_issue(testcase=testcase)
      self.assertTrue(testcase.open)
  
    def test_mark_duplicate_testcase_as_closed_with_no_issue_3(self):
      """Ensure that a duplicate bug older than 7 days, with no associated
      issue does get closed."""
      testcase = test_utils.create_generic_testcase(
          created_days_ago=data_types.DUPLICATE_TESTCASE_NO_BUG_DEADLINE + 1)
      testcase.bug_information = ''
      testcase.status = 'Duplicate'
      testcase.put()
      cleanup.mark_duplicate_testcase_as_closed_with_no_issue(testcase=testcase)
      self.assertFalse(testcase.open)
  
    def test_mark_duplicate_testcase_as_closed_with_no_issue_4(self):
      """Ensure that a duplicate bug 7 days old does not get closed."""
      testcase = test_utils.create_generic_testcase(
          created_days_ago=data_types.DUPLICATE_TESTCASE_NO_BUG_DEADLINE)
      testcase.bug_information = ''
      testcase.status = 'Duplicate'
      testcase.put()
      cleanup.mark_duplicate_testcase_as_closed_with_no_issue(testcase=testcase)
      self.assertTrue(testcase.open)
  
    def test_delete_unreproducible_testcase_with_no_issue_1(self):
      """Ensure that a reproducible bug with no crash in last 7 days, and with an
      associated issue does not get deleted."""
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.delete_unreproducible_testcase_with_no_issue(testcase=testcase)
      self.assertTrue(test_utils.entity_exists(testcase))
  
    def test_delete_unreproducible_testcase_with_no_issue_2(self):
      """Ensure that an unreproducible bug with no crash in last 7 days, with an
      associated issue does not get deleted."""
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.delete_unreproducible_testcase_with_no_issue(testcase=testcase)
      self.assertTrue(test_utils.entity_exists(testcase))
  
    def test_delete_unreproducible_testcase_with_no_issue_3(self):
      """Ensure that an unreproducible bug with no crash in last 7 days, and with
      no associated issue does get deleted."""
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = ''
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.delete_unreproducible_testcase_with_no_issue(testcase=testcase)
      self.assertFalse(test_utils.entity_exists(testcase))
  
    def test_delete_unreproducible_testcase_with_no_issue_4(self):
      """Ensure that an unreproducible bug with crash in last 7 days does not get
      deleted."""
      self.mock.get_crash_occurrence_platforms.return_value = ['Linux']
      testcase = test_utils.create_generic_testcase()
      testcase.one_time_crasher_flag = True
      testcase.bug_information = ''
      testcase.put()
      cleanup.delete_unreproducible_testcase_with_no_issue(testcase=testcase)
      self.assertTrue(test_utils.entity_exists(testcase))
  
    def test_delete_unreproducible_testcase_with_no_issue_5(self):
      """Ensure that an unreproducible bug created in last 7 days and with crash
      seen in last 7 days does not get deleted."""
      self.mock.get_crash_occurrence_platforms.return_value = ['Linux']
      testcase = test_utils.create_generic_testcase(
          created_days_ago=data_types.UNREPRODUCIBLE_TESTCASE_NO_BUG_DEADLINE - 1)
      testcase.one_time_crasher_flag = True
      testcase.bug_information = ''
      testcase.put()
      cleanup.delete_unreproducible_testcase_with_no_issue(testcase=testcase)
      self.assertTrue(test_utils.entity_exists(testcase))
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_1(self):
      """Ensure that we don't close issue if associated testcase is open and
      reproducible."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.open = True
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotEqual(self.issue.status, 'Verified')
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_2(self):
      """Ensure that we don't close issue if associated testcase is open and
      unreproducible."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.open = True
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotEqual(self.issue.status, 'Verified')
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_3(self):
      """Ensure that we close issue if associated testcase is unreproducible, but
      is explicitly marked as closed."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.open = False
      testcase.fixed = 'Yes'
      testcase.put()
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertEqual(self.issue.status, 'Verified')
      self.assertIn('ClusterFuzz testcase 1 is verified as fixed.',
                    self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_4(self):
      """Ensure that we close issue if associated testcase is closed and
      reproducible, and the similar open testcase is unreproducible."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      similar_testcase = test_utils.create_generic_testcase()
      similar_testcase.bug_information = str(self.issue.id)
      similar_testcase.one_time_crasher_flag = True
      similar_testcase.put()
  
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertEqual(self.issue.status, 'Verified')
      self.assertIn(
          'ClusterFuzz testcase 1 is verified as fixed in '
          'https://test-clusterfuzz.appspot.com/revisions'
          '?job=test_content_shell_drt&range=1:2',
          self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_5(self):
      """Ensure that we don't close issue if associated testcase is closed and
      reproducible, but there is a similar testcase is opened."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      similar_testcase = test_utils.create_generic_testcase()
      similar_testcase.bug_information = str(self.issue.id)
      similar_testcase.one_time_crasher_flag = False
      similar_testcase.put()
  
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotEqual(self.issue.status, 'Verified')
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_6(self):
      """Ensure that we close issue if all associated testcases are closed and
      reproducible."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      similar_testcase = test_utils.create_generic_testcase()
      similar_testcase.bug_information = str(self.issue.id)
      similar_testcase.open = False
      similar_testcase.put()
  
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertEqual(self.issue.status, 'Verified')
      self.assertIn(
          'ClusterFuzz testcase 1 is verified as fixed in '
          'https://test-clusterfuzz.appspot.com/revisions'
          '?job=test_content_shell_drt&range=1:2',
          self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_7(self):
      """Ensure that we close issue if issue is marked fixed and all associated
      testcases are closed and reproducible."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      similar_testcase = test_utils.create_generic_testcase()
      similar_testcase.bug_information = str(self.issue.id)
      similar_testcase.open = False
      similar_testcase.put()
  
      self.issue._monorail_issue.open = False
      self.issue.status = 'Fixed'
  
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertEqual(self.issue.status, 'Verified')
      self.assertIn(
          'ClusterFuzz testcase 1 is verified as fixed in '
          'https://test-clusterfuzz.appspot.com/revisions'
          '?job=test_content_shell_drt&range=1:2',
          self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_8(self):
      """Ensure that we don't close issue when we already did the issue
      verification once."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      self.issue.status = 'Assigned'
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_VERIFIED_LABEL])
      ]
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotEqual(self.issue.status, 'Verified')
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_9(self):
      """Ensure that we don't close issue if a developer has labeled the last
      verification as incorrect."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      self.issue.status = 'Assigned'
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_MISTRIAGED_LABEL])
      ]
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotEqual(self.issue.status, 'Verified')
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_10(self):
      """Ensure that we don't close issue when this is unreproducible upload."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.status = 'Unreproducible (trunk)'
      testcase.fixed = ''
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotEqual(self.issue.status, 'Verified')
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_mark_issue_as_closed_if_testcase_is_fixed_11(self):
      """Ensure that we mark issue as verified, but don't close it if job
      definition specifies to skip it."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.fixed = '1:2'
      testcase.open = False
      testcase.one_time_crasher_flag = False
      testcase.put()
  
      data_types.Job(
          name=testcase.job_type,
          platform='LINUX',
          environment_string=('SKIP_AUTO_CLOSE_ISSUE = True\n')).put()
  
      cleanup.mark_issue_as_closed_if_testcase_is_fixed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertIn(
          'ClusterFuzz testcase 1 is verified as fixed in '
          'https://test-clusterfuzz.appspot.com/revisions'
          '?job=test_content_shell_drt&range=1:2',
          self.issue._monorail_issue.comment)
      self.assertEqual(self.issue.status, 'Assigned')
  
    def test_mark_testcase_as_closed_if_issue_is_closed_1(self):
      """Test that we don't do anything if testcase is already closed."""
      testcase = test_utils.create_generic_testcase()
      testcase.open = False
      testcase.put()
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertFalse(testcase.open)
  
    def test_mark_testcase_as_closed_if_issue_is_closed_2(self):
      """Test that we don't do anything if we are unable to get issue object from
      issue tracker."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=None)
      self.assertTrue(testcase.open)
  
    def test_mark_testcase_as_closed_if_issue_is_closed_3(self):
      """Test that we don't do anything if there is no associated issue i.e.
      bug_information is not set."""
      testcase = test_utils.create_generic_testcase()
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_testcase_as_closed_if_issue_is_closed_4(self):
      """Test that we don't do anything if issue is still open."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = True
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_testcase_as_closed_if_issue_is_closed_5(self):
      """Test that we don't do anything if there is a ignore label on issue."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.CLOSE_TESTCASE_WITH_CLOSED_BUG_DEADLINE + 1))
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_IGNORE_LABEL])
      ]
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_testcase_as_closed_if_issue_is_closed_6(self):
      """Test that we don't close testcase if issue is closed <= 2 weeks and
      does not have ignore label."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.CLOSE_TESTCASE_WITH_CLOSED_BUG_DEADLINE))
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_testcase_as_closed_if_issue_is_closed_7(self):
      """Test that we close testcase if issue is closed longer than 2 weeks and
      does not have ignore label."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.CLOSE_TESTCASE_WITH_CLOSED_BUG_DEADLINE + 1))
      cleanup.mark_testcase_as_closed_if_issue_is_closed(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertFalse(testcase.open)
  
    def test_mark_testcase_as_closed_if_job_is_invalid_1(self):
      """Test that we don't close testcase if we have a valid job type."""
      testcase = test_utils.create_generic_testcase()
  
      jobs = [testcase.job_type]
      cleanup.mark_testcase_as_closed_if_job_is_invalid(
          testcase=testcase, jobs=jobs)
      self.assertTrue(testcase.open)
  
    def test_mark_testcase_as_closed_if_job_is_invalid_2(self):
      """Test that we close testcase if we don't have a job type."""
      testcase = test_utils.create_generic_testcase()
  
      jobs = []
      cleanup.mark_testcase_as_closed_if_job_is_invalid(
          testcase=testcase, jobs=jobs)
      self.assertFalse(testcase.open)
  
    def test_mark_unreproducible_testcase_as_fixed_if_issue_is_closed_1(self):
      """Ensure that a reproducible testcase with no associated issue is not
      marked as Fixed."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = ''
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.mark_unreproducible_testcase_as_fixed_if_issue_is_closed(
          testcase=testcase, issue=None)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_as_fixed_if_issue_is_closed_2(self):
      """Ensure that a reproducible testcase with associated issue in open state
      is not marked as Fixed."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.mark_unreproducible_testcase_as_fixed_if_issue_is_closed(
          testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_as_fixed_if_issue_is_closed_3(self):
      """Ensure that a reproducible testcase with associated issue in closed state
      is not marked as Fixed."""
      self.issue._monorail_issue.open = False
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.mark_unreproducible_testcase_as_fixed_if_issue_is_closed(
          testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_as_fixed_if_issue_is_closed_4(self):
      """Ensure that an unreproducible testcase with associated issue in open
      state is marked as Fixed."""
      self.issue._monorail_issue.open = True
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_as_fixed_if_issue_is_closed(
          testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_as_fixed_if_issue_is_closed_5(self):
      """Ensure that an unreproducible testcase with associated issue in closed
      state is marked as Fixed."""
      self.issue._monorail_issue.open = False
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_as_fixed_if_issue_is_closed(
          testcase=testcase, issue=self.issue)
      self.assertFalse(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_1(
        self):
      """Ensure that a reproducible testcase with no associated issue is not
      closed."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = ''
      testcase.one_time_crasher_flag = False
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=None)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_2(
        self):
      """Ensure that an unreproducible testcase with no associated issue is not
      closed."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = ''
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=None)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_3(
        self):
      """Ensure that an unreproducible testcase with a closed issue is not
      closed."""
      self.issue._monorail_issue.open = False
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_4(
        self):
      """Ensure that an unreproducible testcase with an open issue and with crash
      still seen in crash stats is not closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = ['Linux']
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_5(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats, but with other open reproducible testcase is not
      closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      similar_testcase = test_utils.create_generic_testcase()
      similar_testcase.bug_information = str(self.issue.id)
      similar_testcase.one_time_crasher_flag = False
      similar_testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_6(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats, but with mistriaged issue label is not closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_MISTRIAGED_LABEL])
      ]
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_7(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats, and status as Unreproducible does not lead to closing
      of issue."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.status = 'Unreproducible'
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
      self.assertEqual('Assigned', self.issue.status)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_8(
        self):
      """Ensure that an unreproducible testcase with an open issue, created within
      the deadline and crash seen in crash stats is not not closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = ['Linux']
      testcase = test_utils.create_generic_testcase(
          created_days_ago=data_types.UNREPRODUCIBLE_TESTCASE_WITH_BUG_DEADLINE -
          1)
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
      self.assertEqual('Assigned', self.issue.status)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_9(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats, is closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertFalse(testcase.open)
      self.assertEqual('WontFix', self.issue.status)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_10(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats, but with an uploader email is not closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.uploader_email = 'abc@example.com'
      testcase.put()
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
      self.assertEqual('Assigned', self.issue.status)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_11(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats, but reproduced yesterday as as part of progression task
      is not closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      testcase.set_metadata('last_tested_crash_time', test_utils.CURRENT_TIME)
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertTrue(testcase.open)
      self.assertEqual('Assigned', self.issue.status)
  
    def test_mark_unreproducible_testcase_and_issue_as_closed_after_deadline_12(
        self):
      """Ensure that an unreproducible testcase with an open issue, with crash not
      seen in crash stats and progression task is closed."""
      self.issue = appengine_test_utils.create_generic_issue()
      self.mock.get_crash_occurrence_platforms.return_value = []
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.one_time_crasher_flag = True
      testcase.put()
      testcase.set_metadata(
          'last_tested_crash_time',
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.UNREPRODUCIBLE_TESTCASE_WITH_BUG_DEADLINE + 1))
      cleanup.mark_unreproducible_testcase_and_issue_as_closed_after_deadline(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertFalse(testcase.open)
      self.assertEqual('WontFix', self.issue.status)
  
    def test_notify_closed_issue_if_testcase_is_open_1(self):
      """Test that we don't do anything if testcase is already closed."""
      testcase = test_utils.create_generic_testcase()
      testcase.open = False
      testcase.put()
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_2(self):
      """Test that we don't do anything if testcase has status unreproducible
      (upload didn't reproduce)."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.status = 'Unreproducible'
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.NOTIFY_CLOSED_BUG_WITH_OPEN_TESTCASE_DEADLINE + 1))
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_3(self):
      """Test that we don't do anything if we are unable to get issue object from
      issue tracker."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=None)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_4(self):
      """Test that we don't do anything if there is no associated issue i.e.
      bug_information is not set."""
      testcase = test_utils.create_generic_testcase()
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_5(self):
      """Test that we don't do anything if issue is still open."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = True
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_6(self):
      """Test that we don't do anything if we have not exceeded the notification
      deadline."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.NOTIFY_CLOSED_BUG_WITH_OPEN_TESTCASE_DEADLINE))
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_7(self):
      """Test that we don't do anything if there is an ignore label already on
      the issue."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.NOTIFY_CLOSED_BUG_WITH_OPEN_TESTCASE_DEADLINE + 1))
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_IGNORE_LABEL])
      ]
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_8(self):
      """Test that we don't do anything if there is an needs feedback label
      already on the issue."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.NOTIFY_CLOSED_BUG_WITH_OPEN_TESTCASE_DEADLINE + 1))
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_NEEDS_FEEDBACK_LABEL])
      ]
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertNotIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
    def test_notify_closed_issue_if_testcase_is_open_9(self):
      """Test that we add notification if we are past the notification deadline
      and we have not added a needs feedback already."""
      testcase = test_utils.create_generic_testcase()
      testcase.bug_information = str(self.issue.id)
      testcase.put()
      self.issue._monorail_issue.open = False
      self.issue._monorail_issue.closed = (
          test_utils.CURRENT_TIME - datetime.timedelta(
              days=data_types.NOTIFY_CLOSED_BUG_WITH_OPEN_TESTCASE_DEADLINE + 1))
      cleanup.notify_closed_issue_if_testcase_is_open(
          policy=self.policy, testcase=testcase, issue=self.issue)
      self.assertIn(ISSUE_NEEDS_FEEDBACK_LABEL, self.issue.labels)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class UpdateOsLabelsTest(unittest.TestCase):
    """Test updateOsLabels."""
  
    def setUp(self):
      helpers.patch(self, [
          'metrics.crash_stats.get',
      ])
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_no_issue(self):
      """Test no issue."""
      testcase = data_types.Testcase(
          crash_type='type',
          crash_state='state',
          security_flag=True,
          project_name='project')
      testcase.put()
      cleanup.update_os_labels(self.policy, testcase, None)
  
    def test_labels_added(self):
      """Test os label added from crash stats."""
      testcase = data_types.Testcase(
          crash_type='type',
          crash_state='state',
          security_flag=True,
          project_name='project')
      testcase.put()
  
      history = data_types.BuildCrashStatsJobHistory(end_time_in_hours=10000)
      history.put()
  
      rows = [{
          'groups': [{
              'name': 'windows'
          }, {
              'name': 'linux'
          }, {
              'name': 'mac'
          }, {
              'name': 'android:test'
          }, {
              'name': 'android:test2'
          }]
      }]
      self.mock.get.return_value = (1, rows)
  
      issue = appengine_test_utils.create_generic_issue()
      issue._monorail_issue.labels = []
      cleanup.update_os_labels(self.policy, testcase, issue)
      self.assertEqual({'OS-Windows', 'OS-Linux', 'OS-Mac', 'OS-Android'},
                       set(issue.labels))
      self.mock.get.assert_called_once_with(
          end=10000,
          block='day',
          days=1,
          group_by='platform',
          where_clause=('crash_type = "type" AND crash_state = "state" AND '
                        'security_flag = true AND project = "project"'),
          group_having_clause='',
          sort_by='total_count',
          offset=0,
          limit=1)
  
    def test_labels_not_added(self):
      """Test os labels not added from crash stats."""
      testcase = data_types.Testcase(
          crash_type='type',
          crash_state='state',
          security_flag=True,
          project_name='project')
      testcase.put()
  
      history = data_types.BuildCrashStatsJobHistory(end_time_in_hours=10000)
      history.put()
  
      rows = [{
          'groups': [{
              'name': 'windows'
          }, {
              'name': 'linux'
          }, {
              'name': 'mac'
          }, {
              'name': 'android:test'
          }, {
              'name': 'android:test2'
          }]
      }]
      self.mock.get.return_value = (1, rows)
  
      issue = appengine_test_utils.create_generic_issue()
      issue._monorail_issue.labels = []
      comment = appengine_test_utils.create_generic_issue_comment(
          labels=['OS-Mac', 'OS-Android'])
      issue._monorail_issue.comments.append(comment)
      issue.labels.add('OS-Windows')
  
      cleanup.update_os_labels(self.policy, testcase, issue)
      self.assertEqual({'OS-Windows', 'OS-Linux'}, set(issue.labels))
  
  
  @test_utils.with_cloud_emulators('datastore')
  class GetJobsAndPlatformsForTopCrashesTest(unittest.TestCase):
    """Test get_jobs_and_platforms_for_top_crashes."""
  
    def setUp(self):
      data_types.Job(
          name='job1',
          platform='LINUX',
          environment_string=('EXPERIMENTAL = True\n')).put()
      data_types.Job(
          name='job2',
          platform='MAC',
          environment_string=('CUSTOM_BINARY = True\n')).put()
      data_types.Job(
          name='job3',
          platform='WINDOWS',
          environment_string=(
              'SYSTEM_BINARY_DIR = C:\\Program Files\\Internet Explorer\\\n'
          )).put()
      data_types.Job(
          name='job4',
          platform='ANDROID',
          environment_string=('EXCLUDE_FROM_TOP_CRASHES = True\n')).put()
      data_types.Job(name='job5', platform='LINUX', environment_string=('')).put()
      data_types.Job(name='job6', platform='MAC', environment_string=('')).put()
  
    def test(self):
      actual_jobs, actual_platforms = (
          cleanup.get_jobs_and_platforms_for_top_crashes())
      expected_jobs = {'job5', 'job6'}
      expected_platforms = {'LINUX', 'MAC'}
      self.assertEqual(expected_jobs, actual_jobs)
      self.assertEqual(expected_platforms, actual_platforms)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class GetTopCrashesForAllProjectsAndPlatforms(unittest.TestCase):
    """Test get_top_crashes_for_all_projects_and_platforms."""
  
    def setUp(self):
      helpers.patch(self, [
          'metrics.crash_stats.get',
          'metrics.crash_stats.get_last_successful_hour',
      ])
  
      self.top_crashes_rows = [
          {
              'crashState': 'state1',
              'crashType': 'type1',
              'isSecurity': True,
              'totalCount': 350
          },
          {
              'crashState': 'state2',
              'crashType': 'type2',
              'isSecurity': False,
              'totalCount': 450
          },
          {
              'crashState': 'state3',
              'crashType': 'type3',
              'isSecurity': False,
              'totalCount': 250
          },
      ]
      self.mock.get.return_value = (1, self.top_crashes_rows)
      self.mock.get_last_successful_hour.return_value = 10000
      data_types.Job(
          name='job',
          platform='LINUX',
          environment_string=('PROJECT_NAME = project')).put()
  
    def test(self):
      """Test."""
      expected_top_crashes_map = {
          u'project': {
              'LINUX': [{
                  'crashState': 'state1',
                  'crashType': 'type1',
                  'isSecurity': True,
                  'totalCount': 350
              }, {
                  'crashState': 'state2',
                  'crashType': 'type2',
                  'isSecurity': False,
                  'totalCount': 450
              }]
          }
      }
      actual_top_crashes_map = (
          cleanup.get_top_crashes_for_all_projects_and_platforms())
      self.assertEqual(expected_top_crashes_map, actual_top_crashes_map)
      self.mock.get.assert_called_once_with(
          end=10000,
          block='day',
          days=7,
          group_by='platform',
          where_clause=('crash_type NOT IN UNNEST('
                        '["Out-of-memory", "Stack-overflow", "Timeout"]) AND '
                        'crash_state NOT IN UNNEST(["NULL"]) AND '
                        'job_type IN UNNEST(["job"]) AND '
                        'platform LIKE "linux%" AND '
                        'project = "project"'),
          group_having_clause='',
          sort_by='total_count',
          offset=0,
          limit=5)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class UpdateTopCrashLabelsTest(unittest.TestCase):
    """Test update_fuzz_blocker_label."""
  
    def setUp(self):
      helpers.patch_environ(self)
  
      helpers.patch(self, [
          'base.utils.is_oss_fuzz',
          'base.utils.is_chromium',
          'chrome.build_info.get_release_milestone',
      ])
      self.mock.get_release_milestone.return_value = 63
  
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
      self.mock.is_chromium.return_value = True
      self.mock.is_oss_fuzz.return_value = False
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_no_top_crashes(self):
      """Test no label is added if there are no top crashes."""
      top_crashes_by_project_and_platform_map = {u'project': {'LINUX': []}}
  
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertNotIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertNotIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                       self.issue.labels)
      self.assertNotIn('M-63', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_top_crashes_no_match(self):
      """Test no label is added if there are no matching top crashes."""
      top_crashes_by_project_and_platform_map = {
          u'project': {
              'LINUX': [{
                  'crashState': 'state1',
                  'crashType': 'type1',
                  'isSecurity': True,
                  'totalCount': 500
              }]
          }
      }
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertNotIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertNotIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                       self.issue.labels)
      self.assertNotIn('M-63', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_top_crashes_with_testcase_closed(self):
      """Test label is not added if testcase is closed."""
      self.testcase.open = False
      self.testcase.put()
  
      top_crashes_by_project_and_platform_map = {
          u'project': {
              'LINUX': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 350
              }]
          }
      }
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertNotIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertNotIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                       self.issue.labels)
      self.assertNotIn('M-63', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_top_crashes_match_single_platform(self):
      """Test label is added if there is a matching top crash."""
      top_crashes_by_project_and_platform_map = {
          u'project': {
              'LINUX': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 350
              }]
          }
      }
      self.issue.labels.add('M-62')
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                    self.issue.labels)
      self.assertIn('M-63', self.issue.labels)
      self.assertNotIn('M-62', self.issue.labels)
      self.assertEqual(
          'This crash occurs very frequently on linux platform and is likely '
          'preventing the fuzzer fuzzer1 from making much progress. '
          'Fixing this will allow more bugs to be found.'
          '\n\nMarking this bug as a blocker for next Beta release.'
          '\n\nIf this is incorrect, please add the ClusterFuzz-Wrong label and '
          'remove the ReleaseBlock-Beta label.',
          self.issue._monorail_issue.comment)
  
    def test_top_crashes_match_single_platform_oss_fuzz(self):
      """Test label is added if there is a matching top crash for external
      project."""
      self.mock.is_oss_fuzz.return_value = True
      self.testcase.set_metadata('fuzzer_binary_name', 'fuzz_target1')
      top_crashes_by_project_and_platform_map = {
          u'project': {
              'LINUX': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 350
              }]
          }
      }
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertNotIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                       self.issue.labels)
      self.assertNotIn('M-63', self.issue.labels)
      self.assertEqual(
          'This crash occurs very frequently on linux platform and is likely '
          'preventing the fuzzer fuzz_target1 from making much progress. '
          'Fixing this will allow more bugs to be found.'
          '\n\nIf this is incorrect, please file a bug on '
          'https://github.com/google/oss-fuzz/issues/new',
          self.issue._monorail_issue.comment)
  
    def test_top_crashes_match_multiple_platforms(self):
      """Test label is added if there is a matching top crash."""
      top_crashes_by_project_and_platform_map = {
          u'project': {
              'LINUX': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 500
              }],
              'MAC': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 600
              }],
              'WINDOWS': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 700
              }]
          }
      }
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                    self.issue.labels)
      self.assertIn('M-63', self.issue.labels)
      self.assertEqual(
          'This crash occurs very frequently on linux, mac and windows platforms '
          'and is likely preventing the fuzzer fuzzer1 from making much '
          'progress. Fixing this will allow more bugs to be found.'
          '\n\nMarking this bug as a blocker for next Beta release.'
          '\n\nIf this is incorrect, please add the ClusterFuzz-Wrong label and '
          'remove the ReleaseBlock-Beta label.',
          self.issue._monorail_issue.comment)
  
    def test_top_crashes_match_and_label_removed(self):
      """Test label is not added if it was added before and removed."""
      top_crashes_by_project_and_platform_map = {
          u'project': {
              'LINUX': [{
                  'crashState': self.testcase.crash_state,
                  'crashType': self.testcase.crash_type,
                  'isSecurity': self.testcase.security_flag,
                  'totalCount': 500
              }]
          }
      }
  
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_FUZZ_BLOCKER_LABEL])
      ]
      cleanup.update_fuzz_blocker_label(self.policy, self.testcase, self.issue,
                                        top_crashes_by_project_and_platform_map)
      self.assertNotIn(ISSUE_FUZZ_BLOCKER_LABEL, self.issue.labels)
      self.assertNotIn(data_types.CHROMIUM_ISSUE_RELEASEBLOCK_BETA_LABEL,
                       self.issue.labels)
      self.assertNotIn('M-63', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class UpdateComponentsTest(unittest.TestCase):
    """Tests for update_component_labels."""
  
    def setUp(self):
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
  
    def test_components_added(self):
      """Ensure that we add components when applicable."""
      self.testcase.set_metadata(
          'predator_result', {'result': {
              'suspected_components': ['A', 'B>C']
          }})
  
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertIn('A', self.issue.components)
      self.assertIn('B>C', self.issue.components)
      self.assertIn('Test-Predator-Auto-Components', self.issue.labels)
  
    def test_components_not_reapplied(self):
      """Ensure that we don't re-add components once applied."""
      self.testcase.set_metadata(
          'predator_result', {'result': {
              'suspected_components': ['A', 'B>C']
          }})
  
      comment = appengine_test_utils.create_generic_issue_comment(
          labels=['Test-Predator-Auto-Components'])
      self.issue._monorail_issue.comments.append(comment)
  
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertNotIn('A', self.issue.components)
      self.assertNotIn('B>C', self.issue.components)
      self.assertNotIn('Test-Predator-Auto-Components', self.issue.labels)
  
    def test_no_label_added_for_no_components(self):
      """Ensure that we don't add label when there is no component in result."""
      self.testcase.set_metadata('predator_result', {})
      self.issue.components.add('A')
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertIn('A', self.issue.components)
      self.assertNotIn('Test-Predator-Auto-Components', self.issue.labels)
  
    def test_no_label_added_for_same_components(self):
      """Ensure that we don't add label when there is no component in result."""
      self.testcase.set_metadata(
          'predator_result', {'result': {
              'suspected_components': ['A', 'B>C']
          }})
      self.issue.components.add('A')
      self.issue.components.add('B>C')
      self.issue.components.add('D')
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertIn('A', self.issue.components)
      self.assertIn('B>C', self.issue.components)
      self.assertIn('D', self.issue.components)
      self.assertNotIn('Test-Predator-Auto-Components', self.issue.labels)
  
    def test_no_label_added_for_more_specific_component(self):
      """Ensure that we don't add label when there is a more specific component
      already."""
      self.testcase.set_metadata('predator_result',
                                 {'result': {
                                     'suspected_components': ['A']
                                 }})
      self.issue.components.add('A>B')
      self.issue.components.add('D')
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertNotIn('A', self.issue.components)
      self.assertIn('A>B', self.issue.components)
      self.assertIn('D', self.issue.components)
      self.assertNotIn('Test-Predator-Auto-Components', self.issue.labels)
  
    def test_label_added_for_more_specific_component_and_new_component(self):
      """Ensure that we add label when there is a more specific component
      already, but also a new components."""
      self.testcase.set_metadata('predator_result',
                                 {'result': {
                                     'suspected_components': ['A', 'E']
                                 }})
      self.issue.components.add('A>B')
      self.issue.components.add('D')
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertNotIn('A', self.issue.components)
      self.assertIn('A>B', self.issue.components)
      self.assertIn('D', self.issue.components)
      self.assertIn('E', self.issue.components)
      self.assertIn('Test-Predator-Auto-Components', self.issue.labels)
  
    def test_label_added_for_unrelated_component(self):
      """Ensure that we add label when there is a unrelated component with same
      prefix."""
      self.testcase.set_metadata('predator_result',
                                 {'result': {
                                     'suspected_components': ['A']
                                 }})
      self.issue.components.add('AA>B')
      self.issue.components.add('D')
      cleanup.update_component_labels(self.testcase, self.issue)
      self.assertIn('A', self.issue.components)
      self.assertIn('AA>B', self.issue.components)
      self.assertIn('D', self.issue.components)
      self.assertIn('Test-Predator-Auto-Components', self.issue.labels)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class UpdateIssueCCsFromOwnersFileTest(unittest.TestCase):
    """Tests for update_issue_ccs_from_owners_file."""
  
    def setUp(self):
      helpers.patch(self, [
          'base.utils.is_oss_fuzz',
      ])
      helpers.patch_environ(self)
  
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
  
      # We'll generally want to assume we have an unassigned issue.
      self.issue.assignee = ''
      self.issue._monorail_issue.cc = []
      self.issue.status = 'Untriaged'
  
      self.testcase.set_metadata('issue_owners',
                                 'dev1@example1.com,dev2@example2.com')
      self.mock.is_oss_fuzz.return_value = False
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_skipped_issue_closed(self):
      """Test that we don't add ccs to closed issues."""
      self.issue.status = 'Fixed'
      self.issue._monorail_issue.open = False
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual('', self.issue._monorail_issue.comment)
      six.assertCountEqual(self, [], self.issue.ccs)
      self.assertNotIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_skipped_issue_updated_once(self):
      """Test that we don't add ccs if we added ccs once already."""
      comment = appengine_test_utils.create_generic_issue_comment(
          labels=['ClusterFuzz-Auto-CC'])
      self.assertEqual('', self.issue._monorail_issue.comment)
      self.issue._monorail_issue.comments.append(comment)
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      six.assertCountEqual(self, [], self.issue.ccs)
  
    def test_skipped_no_testcase_metadata(self):
      """Test that we don't add ccs if there are no issue_owners key in testcase
      metadata."""
      self.testcase.delete_metadata('issue_owners')
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual('', self.issue._monorail_issue.comment)
      six.assertCountEqual(self, [], self.issue.ccs)
      self.assertNotIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_skipped_empty_testcase_metadata(self):
      """Test that we don't add ccs if owners list is empty in testcase
      metadata."""
      self.testcase.set_metadata('issue_owners', '')
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual('', self.issue._monorail_issue.comment)
      six.assertCountEqual(self, [], self.issue.ccs)
      self.assertNotIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_skipped_ccs_already_added_and_metadata_set(self):
      """Test that we don't add ccs if ccs are added already and metadata has
      has_issue_ccs_from_owners_file attribute."""
      self.testcase.set_metadata('has_issue_ccs_from_owners_file', True)
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual('', self.issue._monorail_issue.comment)
      six.assertCountEqual(self, [], self.issue.ccs)
      self.assertNotIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_skipped_ccs_alread_added_and_metadata_set(self):
      """Test that we don't add ccs if ccs are added already."""
      self.issue.ccs.add('dev1@example1.com')
      self.issue.ccs.add('dev2@example2.com')
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual(
          True, self.testcase.get_metadata('has_issue_ccs_from_owners_file'))
      self.assertEqual('', self.issue._monorail_issue.comment)
      six.assertCountEqual(self, ['dev1@example1.com', 'dev2@example2.com'],
                           sorted(self.issue.ccs))
      self.assertNotIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_add_ccs_with_some_initial_ones(self):
      """Test that we only add new ccs if some are added already."""
      self.issue._monorail_issue.cc = ['dev1@example1.com']
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual(
          'Automatically adding ccs based on OWNERS file / target commit history.'
          '\n\nIf this is incorrect, please add the ClusterFuzz-Wrong label.',
          self.issue._monorail_issue.comment)
      six.assertCountEqual(self, ['dev1@example1.com', 'dev2@example2.com'],
                           sorted(self.issue.ccs))
      self.assertIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_add_ccs_without_any_initial_ones(self):
      """Test adding of ccs with none already existing on the issue."""
      self.mock.is_oss_fuzz.return_value = True
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual(
          'Automatically adding ccs based on OWNERS file / target commit history.'
          '\n\nIf this is incorrect, '
          'please file a bug on https://github.com/google/oss-fuzz/issues/new.',
          self.issue._monorail_issue.comment)
      six.assertCountEqual(self, ['dev1@example1.com', 'dev2@example2.com'],
                           sorted(self.issue.ccs))
      self.assertIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
    def test_only_add_five_random_ccs(self):
      """Test that up to 5 random ccs are added. """
      issue_owners = ['dev%s@example.com' % idx for idx in range(100)]
      self.testcase.set_metadata('issue_owners', ','.join(issue_owners))
  
      helpers.patch(self, ['random.sample'])
      self.mock.sample.side_effect = lambda l, size: l[-size:]
      cleanup.update_issue_ccs_from_owners_file(self.policy, self.testcase,
                                                self.issue)
      self.assertEqual(
          'Automatically adding ccs based on OWNERS file / target commit history.'
          '\n\nIf this is incorrect, please add the ClusterFuzz-Wrong label.',
          self.issue._monorail_issue.comment)
  
      six.assertCountEqual(self, issue_owners[-5:], self.issue.ccs)
      self.assertIn('ClusterFuzz-Auto-CC', self.issue.labels)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class UpdateIssueLabelsForFlakyTestcaseTest(unittest.TestCase):
    """Tests for update_issue_labels_for_flaky_testcase."""
  
    def setUp(self):
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_mark_unreproducible_if_reproducible_change(self):
      """Test that we change label on issue if the testcase is now flaky."""
      self.issue.labels.add('Reproducible')
      self.testcase.one_time_crasher_flag = True
      self.testcase.put()
      cleanup.update_issue_labels_for_flaky_testcase(self.policy, self.testcase,
                                                     self.issue)
  
      self.assertNotIn('Reproducible', self.issue.labels)
      self.assertIn('Unreproducible', self.issue.labels)
      self.assertEqual(
          'ClusterFuzz testcase 1 appears to be flaky, '
          'updating reproducibility label.', self.issue._monorail_issue.comment)
  
    def test_skip_if_unreproducible(self):
      """Test that we don't change labels if the testcase is unreproducible and
      issue is already marked unreproducible."""
      self.issue.labels.add('Unreproducible')
      self.testcase.one_time_crasher_flag = True
      self.testcase.put()
      cleanup.update_issue_labels_for_flaky_testcase(self.policy, self.testcase,
                                                     self.issue)
  
      self.assertNotIn('Reproducible', self.issue.labels)
      self.assertIn('Unreproducible', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_skip_if_reproducible(self):
      """Test that we don't change labels if the testcase is reproducible."""
      self.issue.labels.add('Reproducible')
      self.testcase.one_time_crasher_flag = False
      self.testcase.put()
      cleanup.update_issue_labels_for_flaky_testcase(self.policy, self.testcase,
                                                     self.issue)
  
      self.assertIn('Reproducible', self.issue.labels)
      self.assertNotIn('Unreproducible', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
    def test_skip_if_another_reproducible_testcase(self):
      """Test that we don't change label on issue if another reproducible
      testcase exists."""
      similar_testcase = test_utils.create_generic_testcase()
      similar_testcase.one_time_crasher_flag = False
      similar_testcase.open = True
      similar_testcase.put()
  
      self.issue.labels.add('Reproducible')
      self.testcase.one_time_crasher_flag = True
      cleanup.update_issue_labels_for_flaky_testcase(self.policy, self.testcase,
                                                     self.issue)
  
      self.assertIn('Reproducible', self.issue.labels)
      self.assertNotIn('Unreproducible', self.issue.labels)
      self.assertEqual('', self.issue._monorail_issue.comment)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class UpdateIssueOwnerAndCCsFromPredatorResultsTest(unittest.TestCase):
    """Tests for update_issue_owner_and_ccs_from_predator_results."""
  
    def setUp(self):
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
  
      # We'll generally want to assume we have an unassigned issue.
      self.issue.assignee = ''
      self.issue.status = 'Untriaged'
      self.policy = issue_tracker_policy.get('test-project')
  
      # Set the metadata to a generic result that would lead to an update,
      # assuming no other conditions are violated.
      self.testcase.set_metadata(
          'predator_result', {
              'result': {
                  'suspected_cls': [{
                      'author': 'a@example.com',
                      'description': 'blah',
                      'url': 'url'
                  },]
              }
          })
  
    def test_owner_assigned(self):
      """Ensure that we set the owner when appropriate."""
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, 'a@example.com')
      self.assertEqual(self.issue.status, 'Assigned')
      self.assertIn('Test-Predator-Auto-Owner', self.issue.labels)
  
    def test_single_owner_cced_if_specified(self):
      """Ensure that we cc single authors if assignment is disabled."""
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue, only_allow_ccs=True)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Untriaged')
      self.assertIn('a@example.com', self.issue.ccs)
      self.assertIn('Test-Predator-Auto-CC', self.issue.labels)
  
    def test_closed_not_updated(self):
      """Ensure that we don't set owners for closed issues."""
      self.issue.status = 'Fixed'
      self.issue._monorail_issue.open = False
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Fixed')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
  
    def test_owner_not_reassigned(self):
      """Ensure that we don't overwrite already assigned owners."""
      self.issue.status = 'Assigned'
      self.issue.assignee = 'b@example.com'
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, 'b@example.com')
      self.assertEqual(self.issue.status, 'Assigned')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
  
    def test_skipped_if_already_updated(self):
      """Ensure that we don't try to update the same issue twice."""
      comment = appengine_test_utils.create_generic_issue_comment(
          labels=['Test-Predator-Auto-Owner'])
      self.issue._monorail_issue.comments.append(comment)
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Untriaged')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
  
    def test_skipped_if_previously_assigned(self):
      """Ensure that we don't assign to someone who was already the owner."""
      comment = appengine_test_utils.create_generic_issue_comment()
      comment.owner = 'a@example.com'
      self.issue._monorail_issue.comments.append(comment)
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Untriaged')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
  
    def test_skipped_if_no_cls(self):
      """Ensure that we do nothing if we have no suspected CLs."""
      self.testcase.set_metadata('predator_result',
                                 {'result': {
                                     'suspected_cls': []
                                 }})
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Untriaged')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
  
    def test_add_ccs_if_multiple_cls(self):
      """Ensure that we only cc when we have multiple suspected CLs."""
      self.testcase.set_metadata(
          'predator_result', {
              'result': {
                  'suspected_cls': [
                      {
                          'author': 'a@example.com',
                          'description': 'blah',
                          'url': 'url'
                      },
                      {
                          'author': 'b@example.com',
                          'description': 'halb',
                          'url': 'lru'
                      },
                  ]
              }
          })
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Untriaged')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
      self.assertIn('Test-Predator-Auto-CC', self.issue.labels)
      self.assertIn('a@example.com', self.issue.ccs)
      self.assertIn('b@example.com', self.issue.ccs)
  
    def test_skipped_if_previously_cced_and_metadata_set(self):
      """Ensure that we don't re-cc authors who were cced in the past and have
      has_issue_ccs_from_predator_results set in metadata."""
      self.testcase.set_metadata('has_issue_ccs_from_predator_results', True)
      self.testcase.set_metadata(
          'predator_result', {
              'result': {
                  'suspected_cls': [
                      {
                          'author': 'a@example.com',
                          'description': 'blah',
                          'url': 'url'
                      },
                      {
                          'author': 'b@example.com',
                          'description': 'halb',
                          'url': 'lru'
                      },
                  ]
              }
          })
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue, only_allow_ccs=True)
      self.assertNotIn('a@example.com', self.issue.ccs)
      self.assertNotIn('b@example.com', self.issue.ccs)
      self.assertNotIn('Test-Predator-Auto-CC', self.issue.labels)
  
    def test_skipped_if_previously_cced_and_metadata_not_set(self):
      """Ensure that we don't re-cc authors who were cced in the past."""
      comment = appengine_test_utils.create_generic_issue_comment()
      comment.cc = ['-a@example.com']
      self.issue._monorail_issue.comments.append(comment)
  
      self.testcase.set_metadata(
          'predator_result', {
              'result': {
                  'suspected_cls': [
                      {
                          'author': 'a@example.com',
                          'description': 'blah',
                          'url': 'url'
                      },
                      {
                          'author': 'b@example.com',
                          'description': 'halb',
                          'url': 'lru'
                      },
                  ]
              }
          })
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertNotIn('a@example.com', self.issue.ccs)
      self.assertIn('b@example.com', self.issue.ccs)
      self.assertIn('Test-Predator-Auto-CC', self.issue.labels)
  
    def test_skipped_if_malformed_cl(self):
      """Ensure that we do nothing if the suspected CL is malformed."""
      self.testcase.set_metadata('predator_result',
                                 {'result': {
                                     'suspected_cls': [{
                                         'url': 'url'
                                     },]
                                 }})
  
      cleanup.update_issue_owner_and_ccs_from_predator_results(
          self.policy, self.testcase, self.issue)
      self.assertEqual(self.issue.assignee, '')
      self.assertEqual(self.issue.status, 'Untriaged')
      self.assertNotIn('Test-Predator-Auto-Owner', self.issue.labels)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class NotifyIssueIfTestcaseIsInvalidTest(unittest.TestCase):
    """Tests for notify_issue_if_testcase_is_invalid."""
  
    def setUp(self):
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
  
      # Assume a test case associated with an assigned issue.
      self.issue.status = 'Assigned'
      self.testcase.bug_information = '123456'
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_skipped_if_no_issue(self):
      """Ensure that we handle the case where there is no issue."""
      self.testcase.bug_information = None
  
      # Simply ensure that we don't throw an exception in this case.
      cleanup.notify_issue_if_testcase_is_invalid(self.policy, self.testcase,
                                                  None)
  
    def test_skipped_if_closed_issue(self):
      """Ensure that we ignore issues that are already closed."""
      self.issue.status = 'Fixed'
      self.issue._monorail_issue.open = False
      cleanup.notify_issue_if_testcase_is_invalid(self.policy, self.testcase,
                                                  self.issue)
      self.assertEqual(self.issue._monorail_issue.comment, '')
  
    def test_skipped_if_unmarked_issue(self):
      """Ensure that we ignore issues that have valid fuzzers."""
      cleanup.notify_issue_if_testcase_is_invalid(self.policy, self.testcase,
                                                  self.issue)
      self.assertEqual(self.issue._monorail_issue.comment, '')
  
    def test_notified_if_fuzzer_was_deleted(self):
      """Ensure that we comment on issues that have invalid fuzzers."""
      self.testcase.set_metadata('fuzzer_was_deleted', True)
      cleanup.notify_issue_if_testcase_is_invalid(self.policy, self.testcase,
                                                  self.issue)
      self.assertIn('is associated with an obsolete fuzzer',
                    self.issue._monorail_issue.comment)
      self.assertIn(ISSUE_INVALID_FUZZER_LABEL, self.issue.labels)
  
    def test_not_notified_if_fuzzer_was_deleted_and_notified(self):
      """Ensure that we don't comment again on issues that have invalid fuzzers
      and we have commented once."""
      self.testcase.set_metadata('fuzzer_was_deleted', True)
      self.issue._monorail_issue.comments += [
          appengine_test_utils.create_generic_issue_comment(
              labels=[ISSUE_INVALID_FUZZER_LABEL])
      ]
      cleanup.notify_issue_if_testcase_is_invalid(self.policy, self.testcase,
                                                  self.issue)
      self.assertNotIn('is associated with an obsolete fuzzer',
                       self.issue._monorail_issue.comment)
  
  
  @test_utils.with_cloud_emulators('datastore')
  class NotifyUploaderIfTestcaseIsProcessed(unittest.TestCase):
    """Tests for notify_uploader_when_testcase_is_processed."""
  
    def setUp(self):
      helpers.patch(self, [
          'handlers.cron.cleanup._update_issue_security_severity_and_get_comment',
          'libs.issue_management.issue_filer.update_issue_impact_labels',
          'libs.mail.send',
      ])
  
      self.issue = appengine_test_utils.create_generic_issue()
      self.testcase = test_utils.create_generic_testcase()
      self.testcase_id = self.testcase.key.id()
      self.uploader_email = 'uploader@email.com'
      self.policy = issue_tracker_policy.get('test-project')
  
      data_types.Config(url='url', reproduction_help_url='repro_help_url').put()
  
    def _get_notification(self):
      """Return notification entity for our testcase."""
      return data_types.Notification.query(
          data_types.Notification.testcase_id == self.testcase_id).get()
  
    def test_no_upload_metadata(self):
      """Ensure that we don't send notification if there is no upload metadata."""
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.assertEqual(0, self.mock.send.call_count)
      self.assertIsNone(self._get_notification())
  
    def test_upload_metadata_with_no_uploader_email(self):
      """Ensure that we don't send notification if there is no uploader email."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id, uploader_email=None, bundled=False).put()
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.assertEqual(0, self.mock.send.call_count)
      self.assertIsNone(self._get_notification())
  
    def test_upload_metadata_with_multiple_testcases(self):
      """Ensure that we don't send notification if this a bundled metadata archive
      (with multiple testcases)."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id,
          uploader_email=self.uploader_email,
          bundled=False).put()
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.assertEqual(0, self.mock.send.call_count)
      self.assertIsNone(self._get_notification())
  
    def test_critical_tasks_not_completed(self):
      """Ensure that we don't send notification if critical tasks not complete."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id,
          uploader_email=self.uploader_email,
          bundled=False).put()
      self.testcase.minimized_keys = None
      self.testcase.regression = None
      self.testcase.put()
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.assertEqual(0, self.mock.send.call_count)
  
    def test_pending_testcase(self):
      """Ensure that notification is not sent with a pending testcase."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id,
          uploader_email=self.uploader_email,
          bundled=False).put()
      self.testcase.status = 'Pending'
      self.testcase.one_time_crasher_flag = False
      self.testcase.put()
  
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.assertEqual(0, self.mock.send.call_count)
  
    def test_notification_sent_with_regular_testcase(self):
      """Ensure that notification is sent with a regular testcase."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id,
          uploader_email=self.uploader_email,
          bundled=False).put()
      self.testcase.status = 'Processed'
      self.testcase.minimized_keys = 'some-key'
      self.testcase.regression = '1:2'
      self.testcase.is_impact_set_flag = True
      self.testcase.put()
  
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.mock.send.assert_called_once_with(
          'uploader@email.com', 'Your testcase upload 1 analysis is complete.',
          'Detailed Report: https://test-clusterfuzz.appspot.com/'
          'testcase?key=1<br><br>'
          'Fuzzer: fuzzer1<br>'
          'Job Type: test_content_shell_drt<br>'
          'Crash Type: fake type<br>'
          'Crash Address: 0xdeadbeef<br>'
          'Crash State:<br>'
          '  ...see report...<br>'
          'Sanitizer: address (ASAN)<br><br>'
          'Regressed: https://test-clusterfuzz.appspot.com/revisions?'
          'job=test_content_shell_drt&range=1:2<br><br>'
          'Reproducer Testcase: '
          'https://test-clusterfuzz.appspot.com/download?testcase_id=1<br><br>'
          'See repro_help_url for instructions to reproduce this bug locally.'
          '<br><br>'
          'If you suspect that the result above is incorrect, '
          'try re-doing that job on the testcase report page.')
      self.assertIsNotNone(self._get_notification())
  
    def test_notification_sent_with_unreproducible_testcase(self):
      """Ensure that notification is sent with an unreproducible testcase."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id,
          uploader_email=self.uploader_email,
          bundled=False).put()
      self.testcase.status = 'Unreproducible'
      self.testcase.one_time_crasher_flag = False
      self.testcase.put()
  
      cleanup.notify_uploader_when_testcase_is_processed(
          self.policy, self.testcase, self.issue)
  
      self.mock.send.assert_called_once_with(
          'uploader@email.com', 'Your testcase upload 1 analysis is complete.',
          'Testcase 1 failed to reproduce the crash. '
          'Please inspect the program output at '
          'https://test-clusterfuzz.appspot.com/testcase?key=1.<br><br>'
          'If you suspect that the result above is incorrect, '
          'try re-doing that job on the testcase report page.')
      self.assertIsNotNone(self._get_notification())
  
    def test_notification_sent_with_one_issue_update_when_quiet_flag_set(self):
      """Ensure that notification is sent and issue is updated only once when
      quiet flag is set."""
      data_types.TestcaseUploadMetadata(
          testcase_id=self.testcase_id,
          uploader_email=self.uploader_email,
          bundled=False,
          quiet_flag=True).put()
      self.testcase.status = 'Processed'
      self.testcase.minimized_keys = 'some-key'
      self.testcase.regression = '1:2'
      self.testcase.is_impact_set_flag = True
      self.testcase.put()
  
      for _ in range(3):
        cleanup.notify_uploader_when_testcase_is_processed(
            self.policy, self.testcase, self.issue)
  
      self.assertEqual(
          1, self.mock._update_issue_security_severity_and_get_comment.call_count)
      self.assertIsNotNone(self._get_notification())
  
  
  @test_utils.with_cloud_emulators('datastore')
  class CleanupUnusedFuzzTargetsTest(unittest.TestCase):
    """Tests for cleanup_unused_fuzz_targets_and_jobs."""
  
    def setUp(self):
      helpers.patch_environ(self)
      helpers.patch(self, [
          'base.utils.utcnow',
      ])
      self.mock.utcnow.return_value = datetime.datetime(2018, 1, 31)
  
    def test_cleanup_unused_fuzz_targets_and_jobs(self):
      """Test cleaning up fuzz targets."""
      # FuzzTarget should be removed. All FuzzTargetJobs are older than the
      # threshold.
      data_types.FuzzTarget(
          engine='libFuzzer', binary='binary1', project='test-project').put()
      data_types.FuzzTargetJob(
          fuzz_target_name='libFuzzer_binary1',
          job='job1',
          last_run=datetime.datetime(2018, 1, 15)).put()
  
      # FuzzTarget should be removed. No FuzzTargetJobs.
      data_types.FuzzTarget(
          engine='libFuzzer', binary='binary2', project='test-project').put()
  
      # FuzzTarget should not be removed. Has 1 FuzzTargetJob left after removing
      # old ones.
      data_types.FuzzTarget(
          engine='libFuzzer', binary='binary3', project='test-project').put()
      data_types.FuzzTargetJob(
          fuzz_target_name='libFuzzer_binary3',
          job='job1',
          last_run=datetime.datetime(2018, 1, 20)).put()
      data_types.FuzzTargetJob(
          fuzz_target_name='libFuzzer_binary3',
          job='job2',
          last_run=datetime.datetime(2018, 1, 15)).put()
  
      # FuzzTarget should not be removed. All FuzzTargetJob valid.
      data_types.FuzzTarget(
          engine='libFuzzer', binary='binary4', project='test-project').put()
      data_types.FuzzTargetJob(
          fuzz_target_name='libFuzzer_binary4',
          job='job1',
          last_run=datetime.datetime(2018, 1, 20)).put()
      data_types.FuzzTargetJob(
          fuzz_target_name='libFuzzer_binary4',
          job='job2',
          last_run=datetime.datetime(2018, 1, 20)).put()
  
      cleanup.cleanup_unused_fuzz_targets_and_jobs()
  
      six.assertCountEqual(
          self, ['libFuzzer_binary3', 'libFuzzer_binary4'],
          list([t.key.id() for t in data_types.FuzzTarget.query()]))
      six.assertCountEqual(
          self, [
              'libFuzzer_binary3/job1', 'libFuzzer_binary4/job1',
              'libFuzzer_binary4/job2'
          ], list([t.key.id() for t in data_types.FuzzTargetJob.query()]))
  
  
  @test_utils.with_cloud_emulators('datastore')
  class CleanupUnusedHeartbeatsTest(unittest.TestCase):
    """Tests for cleaning up heartbeat entities."""
  
    def setUp(self):
      helpers.patch_environ(self)
      helpers.patch(self, [
          'base.utils.utcnow',
      ])
  
      self.mock.utcnow.return_value = datetime.datetime(2018, 1, 31)
  
    def test_cleanup(self):
      """Test cleanup_unused_heartbeats."""
      data_types.Heartbeat(last_beat_time=datetime.datetime(2018, 1, 14)).put()
      data_types.Heartbeat(last_beat_time=datetime.datetime(2018, 1, 15)).put()
      data_types.Heartbeat(last_beat_time=datetime.datetime(2018, 1, 16)).put()
      data_types.Heartbeat(last_beat_time=datetime.datetime(2018, 1, 17)).put()
      data_types.Heartbeat(last_beat_time=datetime.datetime(2018, 2, 1)).put()
      cleanup.cleanup_unused_heartbeats()
  
      six.assertCountEqual(self, [
          {
              'task_payload': None,
              'source_version': None,
              'task_end_time': None,
              'last_beat_time': datetime.datetime(2018, 1, 16, 0, 0),
              'bot_name': None
          },
          {
              'task_payload': None,
              'source_version': None,
              'task_end_time': None,
              'last_beat_time': datetime.datetime(2018, 1, 17, 0, 0),
              'bot_name': None
          },
          {
              'task_payload': None,
              'source_version': None,
              'task_end_time': None,
              'last_beat_time': datetime.datetime(2018, 2, 1, 0, 0),
              'bot_name': None
          },
      ], [e.to_dict() for e in data_types.Heartbeat.query()])
  
  
  class UpdateSeverityLabelsTest(unittest.TestCase):
    """Tests for updating severity labels."""
  
    def setUp(self):
      self.testcase = data_types.Testcase()
      self.issue = appengine_test_utils.create_generic_issue()
      self.policy = issue_tracker_policy.get('test-project')
  
    def test_add_missing_severity(self):
      """Test updating missing severity."""
      self.testcase.security_severity = data_types.SecuritySeverity.HIGH
      result = cleanup._update_issue_security_severity_and_get_comment(
          self.policy, self.testcase, self.issue)
      self.assertIn('Security_Severity-High', self.issue.labels)
      self.assertIn('A recommended severity was added to this bug.', result)
  
    def test_add_same_severity(self):
      """Test correct severity already set."""
      self.testcase.security_severity = data_types.SecuritySeverity.HIGH
      self.issue.labels.add('Security_severity-High')
      result = cleanup._update_issue_security_severity_and_get_comment(
          self.policy, self.testcase, self.issue)
      self.assertIn('Security_Severity-High', self.issue.labels)
      self.assertEqual('', result)
  
    def test_add_different_severity(self):
      """Test incorrect severity set."""
      self.testcase.security_severity = data_types.SecuritySeverity.HIGH
      self.issue.labels.add('Security_Severity-Medium')
      result = cleanup._update_issue_security_severity_and_get_comment(
          self.policy, self.testcase, self.issue)
      self.assertNotIn('Security_Severity-High', self.issue.labels)
      self.assertIn('Security_Severity-Medium', self.issue.labels)
      self.assertIn('different from what was assigned', result)
  