# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import mock

from analysis.analysis_testcase import AnalysisTestCase
from analysis.clusterfuzz_data import ClusterfuzzData
from analysis.clusterfuzz_data import GetCommitCountInRegressionRange
from analysis.clusterfuzz_parser import ClusterfuzzParser
from analysis.stacktrace import CallStack
from analysis.stacktrace import StackFrame
from analysis.stacktrace import Stacktrace
from analysis.type_enums import CrashClient
from analysis.type_enums import SanitizerType
from libs.deps.chrome_dependency_fetcher import ChromeDependencyFetcher
from libs.deps.dependency import Dependency
from libs.deps.dependency import DependencyRoll


class CusterfuzzDataTest(AnalysisTestCase):
  """Tests ``ClusterfuzzData`` class."""

  def testProperties(self):
    """Tests ``ClusterfuzzData`` specific properties."""
    regression_range = {'repo_url': 'http://repo',
                        'repo_path': 'src',
                        'old_revision': 'rev2',
                        'new_revision': 'rev7'}
    raw_crash_data = self.GetDummyClusterfuzzData(
        sanitizer='ASAN', regression_range=regression_range)
    crash_data = ClusterfuzzData(raw_crash_data, self.GetMockRepoFactory())
    self.assertEqual(crash_data.crash_address,
                     raw_crash_data['customized_data']['crash_address'])
    self.assertEqual(crash_data.crash_type,
                     raw_crash_data['customized_data']['crash_type'])
    self.assertEqual(crash_data.sanitizer,
                     SanitizerType.ADDRESS_SANITIZER)
    self.assertEqual(crash_data.job_type,
                     raw_crash_data['customized_data']['job_type'])
    self.assertEqual(crash_data.regression_range,
                     ('rev2', 'rev7'))
    self.assertEqual(crash_data.testcase_id,
                     raw_crash_data['customized_data']['testcase_id'])
    self.assertEqual(crash_data.security_flag,
                     raw_crash_data['customized_data']['security_flag'])

    raw_crash_data['customized_data']['regression_range'] = None
    crash_data = ClusterfuzzData(raw_crash_data, self.GetMockRepoFactory())
    self.assertIsNone(crash_data.regression_range)

  @mock.patch('analysis.clusterfuzz_parser.ClusterfuzzParser.Parse')
  def testParseStacktraceFailed(self, mock_parse):
    """Tests that ``stacktrace`` is None when failed to pars stacktrace."""
    mock_parse.return_value = None
    crash_data = ClusterfuzzData(self.GetDummyClusterfuzzData(),
                                 self.GetMockRepoFactory())
    self.assertIsNone(crash_data.stacktrace)

  def testParseStacktraceSucceeded(self):
    """Tests parsing ``stacktrace``."""
    crash_data = ClusterfuzzData(self.GetDummyClusterfuzzData(),
                                 self.GetMockRepoFactory())
    stack = CallStack(0)
    stacktrace = Stacktrace([stack], stack)
    with mock.patch(
        'analysis.clusterfuzz_parser.ClusterfuzzParser.Parse') as mock_parse:
      mock_parse.return_value = stacktrace
      self._VerifyTwoStacktracesEqual(crash_data.stacktrace, stacktrace)

  def testDependencies(self):
    """Tests ``dependencies`` property."""
    dep = Dependency('src/', 'https://repo', 'rev1')
    crash_data = ClusterfuzzData(self.GetDummyClusterfuzzData(
        dependencies=[{'dep_path': dep.path,
                       'repo_url': dep.repo_url,
                       'revision': dep.revision}]),
                                 self.GetMockRepoFactory())

    self.assertEqual(len(crash_data.dependencies), 1)
    self.assertTrue(dep.path in crash_data.dependencies)
    self.assertEqual(crash_data.dependencies[dep.path].path, dep.path)
    self.assertEqual(crash_data.dependencies[dep.path].repo_url, dep.repo_url)
    self.assertEqual(crash_data.dependencies[dep.path].revision, dep.revision)

  def testDependencyRolls(self):
    """Tests ``regression_rolls`` property."""
    dep_roll = DependencyRoll('src/', 'https://repo', 'rev1', 'rev6')
    crash_data = ClusterfuzzData(self.GetDummyClusterfuzzData(
        dependency_rolls=[{'dep_path': dep_roll.path,
                           'repo_url': dep_roll.repo_url,
                           'old_revision': dep_roll.old_revision,
                           'new_revision': dep_roll.new_revision}]),
                                 self.GetMockRepoFactory())

    self.assertEqual(len(crash_data.dependency_rolls), 1)
    self.assertTrue(dep_roll.path in crash_data.dependency_rolls)
    self.assertEqual(crash_data.dependency_rolls[dep_roll.path].path,
                     dep_roll.path)
    self.assertEqual(crash_data.dependency_rolls[dep_roll.path].repo_url,
                     dep_roll.repo_url)
    self.assertEqual(crash_data.dependency_rolls[dep_roll.path].old_revision,
                     dep_roll.old_revision)
    self.assertEqual(crash_data.dependency_rolls[dep_roll.path].new_revision,
                     dep_roll.new_revision)

  def testIdentifiers(self):
    crash_data = ClusterfuzzData(
        self.GetDummyClusterfuzzData(), self.GetMockRepoFactory())

    self.assertEqual(crash_data.identifiers, crash_data.testcase_id)

  def testRegressionRangeProperty(self):
    regression_range = {'dep_path': 'src', 'repo_url': 'https://repo',
                        'old_revision': 'rev0', 'new_revision': 'rev3'}
    crash_data = ClusterfuzzData(
        self.GetDummyClusterfuzzData(regression_range=regression_range),
        self.GetMockRepoFactory())

    self.assertEqual(crash_data.regression_range,
                     (regression_range['old_revision'],
                      regression_range['new_revision']))

    self.assertEqual(crash_data.regression_repository, regression_range)

  def testGetCommitCountInRegressionRange(self):
    """Tests ``GetCommitCountInRegressionRange`` function."""
    regression_range = {'repo_url': 'http://repo',
                        'repo_path': 'src',
                        'old_revision': 'rev2',
                        'new_revision': 'rev7'}

    def GetRepository(_):

      class MockRepository(object):
        def GetCommitsBetweenRevisions(self, *_):
          return ['rev1', 'rev2', 'rev3']

      return MockRepository()

    self.assertEqual(
        GetCommitCountInRegressionRange(
            GetRepository,
            regression_range['repo_url'], regression_range['old_revision'],
            regression_range['new_revision']), 3)

  def testCommitCountInRegressionRangeReturns0ForEmptyRegressionRange(self):
    """Tests ``commit_count_in_regression_range`` property."""
    regression_range = {'dep_path': 'src', 'repo_url': 'https://repo',
                        'old_revision': None, 'new_revision': None}
    crash_data = ClusterfuzzData(
        self.GetDummyClusterfuzzData(regression_range=regression_range),
        self.GetMockRepoFactory())

    self.assertEqual(crash_data.commit_count_in_regression_range, 0)

  @mock.patch('analysis.clusterfuzz_data.GetCommitCountInRegressionRange')
  def testCommitCountInRegressionRangeProperty(self, mock_get_commits):
    """Tests ``commit_count_in_regression_range`` property."""
    mock_get_commits.return_value = 6
    regression_range = {'dep_path': 'src', 'repo_url': 'https://repo',
                        'old_revision': 'rev0', 'new_revision': 'rev3'}
    crash_data = ClusterfuzzData(
        self.GetDummyClusterfuzzData(regression_range=regression_range),
        self.GetMockRepoFactory())

    self.assertEqual(crash_data.commit_count_in_regression_range, 6)

  def testAlwaysRedoClusterfuzzAnalysis(self):
    """Tests the redo of ClusterfuzzData is always True."""
    crash_data = ClusterfuzzData(
        self.GetDummyClusterfuzzData(), self.GetMockRepoFactory())
    self.assertTrue(crash_data.redo)
