#!/usr/bin/env python3
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import datetime
import os.path
import sys
import tempfile
import unittest

sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))

from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi, MockFile
from build.ios import presubmit_support

_TEMP_FILELIST_CONTENTS = """# Copyright %d The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
# NOTE: this file is generated by build/ios/update_bundle_filelist.py
#       If it requires updating, you should get a presubmit error with
#       instructions on how to regenerate. Otherwise, do not edit.
""" % (datetime.datetime.now().year)

_TEMP_GLOBLIST_CONTENTS = """**
-*.globlist
-*.filelist
"""


class BundleDataPresubmit(unittest.TestCase):
    def setUp(self):
        self.mock_input_api = MockInputApi()
        self.mock_input_api.change.RepositoryRoot = lambda: os.path.join(
            os.path.dirname(__file__), '..', '..'
        )
        self.mock_input_api.PresubmitLocalPath = lambda: os.path.dirname(
            __file__
        )
        self.mock_output_api = MockOutputApi()

    def testBasic(self):
        """
        Checks that a glob can be expanded to build a file list and if it
        matches the existing file list, we should see no error.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api, self.mock_output_api, 'test_data/basic', '.'
        )
        self.assertEqual([], results)

    def testExclusion(self):
        """
        Check that globs can be used to exclude files from file lists.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api,
            self.mock_output_api,
            'test_data/exclusions',
            '.',
        )
        self.assertEqual([], results)

    def testDifferentLocalPath(self):
        """
        Checks the case where the presubmit directory is not the same as the
        globroot, but it is still local (i.e., not relative to the repository
        root)
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api,
            self.mock_output_api,
            'test_data/different_local_path',
            'test_data',
        )
        self.assertEqual([], results)

    def testRepositoryRelative(self):
        """
        Checks the case where globs are relative to the repository root.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api,
            self.mock_output_api,
            'test_data/repository_relative',
        )
        self.assertEqual([], results)

    def testMissingFilesInFilelist(self):
        """
        Checks that we do indeed return an error if the filelist is missing a
        file. In this case, all of the test .filelist and .globlist files are
        excluded.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api, self.mock_output_api, 'test_data/missing', '.'
        )
        self.assertEqual(1, len(results))

    def testExtraFilesInFilelist(self):
        """
        Checks the case where extra files have been included in the file list.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api, self.mock_output_api, 'test_data/extra', '.'
        )
        self.assertEqual(1, len(results))

    def testOrderInsensitive(self):
        """
        Checks that we do not trigger an error for cases where the file list is
        correct, but in a different order than the globlist expansion.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api, self.mock_output_api, 'test_data/reorder', '.'
        )
        self.assertEqual([], results)

    def testUnexpectedHeader(self):
        """
        Checks an unexpected header in a file list causes an error.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api, self.mock_output_api, 'test_data/comment', '.'
        )
        self.assertEqual(1, len(results))

    def testUntrackedFiles(self):
        """
        Checks that the untracked files are correctly ignored.
        """
        with tempfile.TemporaryDirectory() as temp_dir:
            with open(os.path.join(temp_dir, 'untracked.filelist'), 'w') as f:
                f.write(_TEMP_FILELIST_CONTENTS)
            with open(os.path.join(temp_dir, 'untracked.globlist'), 'w') as f:
                f.write(_TEMP_GLOBLIST_CONTENTS)
            with open(os.path.join(temp_dir, 'untracked.txt'), 'w') as f:
                f.write('Hello, World!')
            path = os.path.join(temp_dir, 'untracked')
            self.mock_input_api.change.RepositoryRoot = lambda: temp_dir
            self.mock_input_api.PresubmitLocalPath = lambda: temp_dir
            results = presubmit_support.CheckBundleData(
                self.mock_input_api, self.mock_output_api, 'untracked'
            )
            self.assertEqual([], results)

    def testExcludeDuplicates(self):
        """
        Checks that duplicate filenames are not added to a filelist.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api,
            self.mock_output_api,
            'test_data/duplicates',
            '.',
        )
        self.assertEqual([], results)

    def testCheckOutsideGloblistDir(self):
        """
        Checks that including files outside the globlist directory is an error.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api,
            self.mock_output_api,
            'test_data/outside_globlist_dir',
            '.',
        )
        self.assertEqual(1, len(results))

    def testCheckIgnoreOutsideGloblistDir(self):
        """
        Checks that files outside the globlist directory can be ignored.
        """
        results = presubmit_support.CheckBundleData(
            self.mock_input_api,
            self.mock_output_api,
            'test_data/ignore_outside_globlist_dir',
            '.',
        )
        self.assertEqual([], results)


class NotFatalUntilAdoptionTest(unittest.TestCase):
    """Test the CheckNotFatalUntilAdoption presubmit check."""

    def setUp(self):
        self.mock_input_api = MockInputApi()
        self.mock_input_api.change.RepositoryRoot = lambda: os.path.join(
            os.path.dirname(__file__), '..', '..'
        )
        self.mock_output_api = MockOutputApi()

    def testNewCheckWithoutNFU(self):
        file = MockFile(
            'ios/web_view/test.cc', ['  CHECK(condition);'], action='A'
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckNotFatalUntilAdoption(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 1)
        self.assertEqual('warning', errors[0].type)
        self.assertTrue(
            'Consider using base::NotFatalUntil' in errors[0].message
        )
        self.assertTrue(
            '//ios/web_view is used as a shared framework by other embedders'
            in errors[0].message
        )
        self.assertTrue(
            file.LocalPath() + ':1: CHECK(condition);' in errors[0].message
        )

    def testNewCheckWithNFU(self):
        file = MockFile(
            'ios/web_view/test.cc',
            ['  CHECK(condition) << base::NotFatalUntil(2024, 10);'],
            action='A',
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckNotFatalUntilAdoption(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 0)

    def testCheckPromotedFromNFU(self):
        file = MockFile(
            'ios/web_view/test.cc',
            ['  CHECK(condition);'],
            old_contents=[
                '  CHECK(condition) << base::NotFatalUntil(2024, 10);'
            ],
            action='M',
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckNotFatalUntilAdoption(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 0)

    def testCheckModifiedWithoutPriorNFU(self):
        # If a CHECK was already fatal (no NFU) and is modified, it should warn.
        file = MockFile(
            'ios/web_view/test.cc',
            ['  CHECK(new_condition);'],
            old_contents=['  CHECK(old_condition);'],
            action='M',
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckNotFatalUntilAdoption(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 1)

    def testMultilineNFU(self):
        file = MockFile(
            'ios/web_view/test.cc',
            ['  CHECK(condition)', '      << base::NotFatalUntil(2024, 10);'],
            action='A',
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckNotFatalUntilAdoption(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 0)

    def testCheckInComment(self):
        file = MockFile(
            'ios/web_view/test.cc', ['  // CHECK(condition);'], action='A'
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckNotFatalUntilAdoption(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 0)


class DiscourageCheckDerefTest(unittest.TestCase):
    """Test the CheckDiscourageCheckDeref presubmit check."""

    def setUp(self):
        self.mock_input_api = MockInputApi()
        self.mock_input_api.change.RepositoryRoot = lambda: os.path.join(
            os.path.dirname(__file__), '..', '..'
        )
        self.mock_output_api = MockOutputApi()

    def testCheckDerefUsed(self):
        file = MockFile(
            'ios/web_view/test.cc', ['  CHECK_DEREF(ptr);'], action='A'
        )
        self.mock_input_api.InitFiles([file])
        errors = presubmit_support.CheckDiscourageCheckDeref(
            self.mock_input_api, self.mock_output_api
        )
        self.assertEqual(len(errors), 1)
        self.assertEqual('warning', errors[0].type)
        self.assertTrue(
            'Avoid using CHECK_DEREF in iOS/web_view code' in errors[0].message
        )
        self.assertTrue(
            file.LocalPath() + ':1: CHECK_DEREF(ptr);' in errors[0].message
        )


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