#!/usr/bin/env vpython3

#  Copyright 2017 The WebRTC project authors. All Rights Reserved.
#
#  Use of this source code is governed by a BSD-style license
#  that can be found in the LICENSE file in the root of the source
#  tree. An additional intellectual property rights grant can be found
#  in the file PATENTS.  All contributing project authors may
#  be found in the AUTHORS file in the root of the source tree.

from __future__ import absolute_import
import os
import shutil
import tempfile
import textwrap
import unittest

import PRESUBMIT
# pylint: disable=line-too-long
from presubmit_test_mocks import MockInputApi, MockOutputApi, MockFile, MockChange


class CheckBugEntryFieldTest(unittest.TestCase):
  def testCommitMessageBugEntryWithNoError(self):
    mock_input_api = MockInputApi()
    mock_output_api = MockOutputApi()
    mock_input_api.change = MockChange([], ['webrtc:1234'])
    errors = PRESUBMIT.CheckCommitMessageBugEntry(mock_input_api,
                                                  mock_output_api)
    self.assertEqual(0, len(errors))

  def testCommitMessageBugEntryReturnError(self):
    mock_input_api = MockInputApi()
    mock_output_api = MockOutputApi()
    mock_input_api.change = MockChange([], ['webrtc:1234', 'webrtc=4321'])
    errors = PRESUBMIT.CheckCommitMessageBugEntry(mock_input_api,
                                                  mock_output_api)
    self.assertEqual(1, len(errors))
    self.assertEqual(('Bogus Bug entry: webrtc=4321. Please specify'
                      ' the issue tracker prefix and the issue number,'
                      ' separated by a colon, e.g. webrtc:123 or'
                      ' chromium:12345.'), str(errors[0]))

  def testCommitMessageBugEntryWithoutPrefix(self):
    mock_input_api = MockInputApi()
    mock_output_api = MockOutputApi()
    mock_input_api.change = MockChange([], ['1234'])
    errors = PRESUBMIT.CheckCommitMessageBugEntry(mock_input_api,
                                                  mock_output_api)
    self.assertEqual(1, len(errors))
    self.assertEqual(('Bug entry requires issue tracker prefix, '
                      'e.g. webrtc:1234'), str(errors[0]))

  def testCommitMessageBugEntryIsNone(self):
    mock_input_api = MockInputApi()
    mock_output_api = MockOutputApi()
    mock_input_api.change = MockChange([], ['None'])
    errors = PRESUBMIT.CheckCommitMessageBugEntry(mock_input_api,
                                                  mock_output_api)
    self.assertEqual(0, len(errors))

  def testCommitMessageBugEntrySupportInternalBugReference(self):
    mock_input_api = MockInputApi()
    mock_output_api = MockOutputApi()
    mock_input_api.change.BUG = 'b/12345'
    errors = PRESUBMIT.CheckCommitMessageBugEntry(mock_input_api,
                                                  mock_output_api)
    self.assertEqual(0, len(errors))
    mock_input_api.change.BUG = 'b/12345, webrtc:1234'
    errors = PRESUBMIT.CheckCommitMessageBugEntry(mock_input_api,
                                                  mock_output_api)
    self.assertEqual(0, len(errors))


class CheckNewlineAtTheEndOfProtoFilesTest(unittest.TestCase):
  def setUp(self):
    self.tmp_dir = tempfile.mkdtemp()
    self.proto_file_path = os.path.join(self.tmp_dir, 'foo.proto')
    self.input_api = MockInputApi()
    self.output_api = MockOutputApi()

  def tearDown(self):
    shutil.rmtree(self.tmp_dir, ignore_errors=True)

  def testErrorIfProtoFileDoesNotEndWithNewline(self):
    self._GenerateProtoWithoutNewlineAtTheEnd()
    self.input_api.files = [MockFile(self.proto_file_path)]
    errors = PRESUBMIT.CheckNewlineAtTheEndOfProtoFiles(
        self.input_api, self.output_api, lambda x: True)
    self.assertEqual(1, len(errors))
    self.assertEqual(
        'File %s must end with exactly one newline.' % self.proto_file_path,
        str(errors[0]))

  def testNoErrorIfProtoFileEndsWithNewline(self):
    self._GenerateProtoWithNewlineAtTheEnd()
    self.input_api.files = [MockFile(self.proto_file_path)]
    errors = PRESUBMIT.CheckNewlineAtTheEndOfProtoFiles(
        self.input_api, self.output_api, lambda x: True)
    self.assertEqual(0, len(errors))

  def _GenerateProtoWithNewlineAtTheEnd(self):
    with open(self.proto_file_path, 'w') as f:
      f.write(
          textwrap.dedent("""
        syntax = "proto2";
        option optimize_for = LITE_RUNTIME;
        package webrtc.audioproc;
      """))

  def _GenerateProtoWithoutNewlineAtTheEnd(self):
    with open(self.proto_file_path, 'w') as f:
      f.write(
          textwrap.dedent("""
        syntax = "proto2";
        option optimize_for = LITE_RUNTIME;
        package webrtc.audioproc;"""))


class CheckNoMixingSourcesTest(unittest.TestCase):
  def setUp(self):
    self.tmp_dir = tempfile.mkdtemp()
    self.file_path = os.path.join(self.tmp_dir, 'BUILD.gn')
    self.input_api = MockInputApi()
    self.output_api = MockOutputApi()

  def tearDown(self):
    shutil.rmtree(self.tmp_dir, ignore_errors=True)

  def testErrorIfCAndCppAreMixed(self):
    self._AssertNumberOfErrorsWithSources(1, ['foo.c', 'bar.cc', 'bar.h'])

  def testErrorIfCAndObjCAreMixed(self):
    self._AssertNumberOfErrorsWithSources(1, ['foo.c', 'bar.m', 'bar.h'])

  def testErrorIfCAndObjCppAreMixed(self):
    self._AssertNumberOfErrorsWithSources(1, ['foo.c', 'bar.mm', 'bar.h'])

  def testErrorIfCppAndObjCAreMixed(self):
    self._AssertNumberOfErrorsWithSources(1, ['foo.cc', 'bar.m', 'bar.h'])

  def testErrorIfCppAndObjCppAreMixed(self):
    self._AssertNumberOfErrorsWithSources(1, ['foo.cc', 'bar.mm', 'bar.h'])

  def testNoErrorIfOnlyC(self):
    self._AssertNumberOfErrorsWithSources(0, ['foo.c', 'bar.c', 'bar.h'])

  def testNoErrorIfOnlyCpp(self):
    self._AssertNumberOfErrorsWithSources(0, ['foo.cc', 'bar.cc', 'bar.h'])

  def testNoErrorIfOnlyObjC(self):
    self._AssertNumberOfErrorsWithSources(0, ['foo.m', 'bar.m', 'bar.h'])

  def testNoErrorIfOnlyObjCpp(self):
    self._AssertNumberOfErrorsWithSources(0, ['foo.mm', 'bar.mm', 'bar.h'])

  def testNoErrorIfObjCAndObjCppAreMixed(self):
    self._AssertNumberOfErrorsWithSources(0, ['foo.m', 'bar.mm', 'bar.h'])

  def testNoErrorIfSourcesAreInExclusiveIfBranches(self):
    self._GenerateBuildFile(
        textwrap.dedent("""
      rtc_library("bar_foo") {
        if (is_win) {
          sources = [
            "bar.cc",
          ],
        }
        if (is_ios) {
          sources = [
            "bar.mm",
          ],
        }
      }
      rtc_library("foo_bar") {
        if (is_win) {
          sources = [
            "foo.cc",
          ],
        }
        if (is_ios) {
          sources = [
            "foo.mm",
          ],
        }
      }
    """))
    self.input_api.files = [MockFile(self.file_path)]
    errors = PRESUBMIT.CheckNoMixingSources(self.input_api,
                                            [MockFile(self.file_path)],
                                            self.output_api)
    self.assertEqual(0, len(errors))

  def testErrorIfSourcesAreNotInExclusiveIfBranches(self):
    self._GenerateBuildFile(
        textwrap.dedent("""
      rtc_library("bar_foo") {
        if (is_win) {
          sources = [
            "bar.cc",
          ],
        }
        if (foo_bar) {
          sources += [
            "bar.mm",
          ],
        }
      }
      rtc_library("foo_bar") {
        if (is_win) {
          sources = [
            "foo.cc",
          ],
        }
        if (foo_bar) {
          sources += [
            "foo.mm",
          ],
        }
        if (is_ios) {
          sources = [
            "bar.m",
            "bar.c",
          ],
        }
      }
    """))
    self.input_api.files = [MockFile(self.file_path)]
    errors = PRESUBMIT.CheckNoMixingSources(self.input_api,
                                            [MockFile(self.file_path)],
                                            self.output_api)
    self.assertEqual(1, len(errors))
    self.assertTrue('bar.cc' in str(errors[0]))
    self.assertTrue('bar.mm' in str(errors[0]))
    self.assertTrue('foo.cc' in str(errors[0]))
    self.assertTrue('foo.mm' in str(errors[0]))
    self.assertTrue('bar.m' in str(errors[0]))
    self.assertTrue('bar.c' in str(errors[0]))

  def _AssertNumberOfErrorsWithSources(self, number_of_errors, sources):
    assert len(sources) == 3, 'This function accepts a list of 3 source files'
    self._GenerateBuildFile(
        textwrap.dedent("""
      rtc_static_library("bar_foo") {
        sources = [
          "%s",
          "%s",
          "%s",
        ],
      }
      rtc_library("foo_bar") {
        sources = [
          "%s",
          "%s",
          "%s",
        ],
      }
    """ % (tuple(sources) * 2)))
    self.input_api.files = [MockFile(self.file_path)]
    errors = PRESUBMIT.CheckNoMixingSources(self.input_api,
                                            [MockFile(self.file_path)],
                                            self.output_api)
    self.assertEqual(number_of_errors, len(errors))
    if number_of_errors == 1:
      for source in sources:
        if not source.endswith('.h'):
          self.assertTrue(source in str(errors[0]))

  def _GenerateBuildFile(self, content):
    with open(self.file_path, 'w') as f:
      f.write(content)


class CheckAssertUsageTest(unittest.TestCase):
  def setUp(self):
    self.input_api = MockInputApi()
    self.output_api = MockOutputApi()
    self._content_with_assert = ['void Foo() {', '    assert(true);', '}']
    self._content_without_assert = ['void Foo() {', '    RTC_CHECK(true);', '}']

  def testDetectsAssertInCcFile(self):
    self.input_api.files = [
        MockFile('with_assert.cc', self._content_with_assert),
        MockFile('without_assert.cc', self._content_without_assert),
    ]
    errors = PRESUBMIT.CheckAssertUsage(self.input_api,
                                        self.output_api, lambda x: True)
    self.assertEqual(1, len(errors))
    self.assertEqual('with_assert.cc', errors[0].items[0])

  def testDetectsAssertInHeaderFile(self):
    self.input_api.files = [
        MockFile('with_assert.h', self._content_with_assert),
        MockFile('without_assert.h', self._content_without_assert),
    ]
    errors = PRESUBMIT.CheckAssertUsage(self.input_api,
                                        self.output_api, lambda x: True)
    self.assertEqual(1, len(errors))
    self.assertEqual('with_assert.h', errors[0].items[0])

  def testDetectsAssertInObjCFile(self):
    self.input_api.files = [
        MockFile('with_assert.m', self._content_with_assert),
        MockFile('without_assert.m', self._content_without_assert),
    ]
    errors = PRESUBMIT.CheckAssertUsage(self.input_api,
                                        self.output_api, lambda x: True)
    self.assertEqual(1, len(errors))
    self.assertEqual('with_assert.m', errors[0].items[0])

  def testDetectsAssertInObjCppFile(self):
    self.input_api.files = [
        MockFile('with_assert.mm', self._content_with_assert),
        MockFile('without_assert.mm', self._content_without_assert),
    ]
    errors = PRESUBMIT.CheckAssertUsage(self.input_api,
                                        self.output_api, lambda x: True)
    self.assertEqual(1, len(errors))
    self.assertEqual('with_assert.mm', errors[0].items[0])

  def testDoesntDetectAssertInOtherFiles(self):
    self.input_api.files = [
        MockFile('with_assert.cpp', self._content_with_assert),
    ]
    errors = PRESUBMIT.CheckAssertUsage(self.input_api,
                                        self.output_api, lambda x: True)
    self.assertEqual(0, len(errors))


if __name__ == '__main__':
  unittest.main()
