# pylint: disable=g-backslash-continuation
# Copyright 2023 The Bazel Authors. All rights reserved.
#
# 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.
# pylint: disable=g-long-ternary

import os
import tempfile
import unittest

from src.test.py.bazel import test_base
from src.test.py.bazel.bzlmod.test_utils import BazelRegistry


class BazelYankedVersionsTest(test_base.TestBase):

  def setUp(self):
    test_base.TestBase.setUp(self)
    self.registries_work_dir = tempfile.mkdtemp(dir=self._test_cwd)
    self.main_registry = BazelRegistry(
        os.path.join(self.registries_work_dir, 'main')
    )
    self.main_registry.createCcModule('aaa', '1.0').createCcModule(
        'aaa', '1.1'
    ).createCcModule('bbb', '1.0', {'aaa': '1.0'}).createCcModule(
        'bbb', '1.1', {'aaa': '1.1'}
    ).createCcModule(
        'ccc', '1.1', {'aaa': '1.1', 'bbb': '1.1'}
    ).createCcModule(
        'ddd', '1.0', {'yanked1': '1.0', 'yanked2': '1.0'}
    ).createCcModule(
        'eee', '1.0', {'yanked1': '1.0'}
    ).createCcModule(
        'yanked1', '1.0'
    ).createCcModule(
        'yanked2', '1.0'
    ).addMetadata(
        'yanked1', yanked_versions={'1.0': 'dodgy'}
    ).addMetadata(
        'yanked2', yanked_versions={'1.0': 'sketchy'}
    )
    self.writeBazelrcFile()
    self.ScratchFile('WORKSPACE')
    # The existence of WORKSPACE.bzlmod prevents WORKSPACE prefixes or suffixes
    # from being used; this allows us to test built-in modules actually work
    self.ScratchFile('WORKSPACE.bzlmod')

  def writeBazelrcFile(self, allow_yanked_versions=True):
    self.ScratchFile(
        '.bazelrc',
        [
            # In ipv6 only network, this has to be enabled.
            # 'startup --host_jvm_args=-Djava.net.preferIPv6Addresses=true',
            'build --enable_bzlmod',
            'build --registry=' + self.main_registry.getURL(),
            # We need to have BCR here to make sure built-in modules like
            # bazel_tools can work.
            'build --registry=https://bcr.bazel.build',
            'build --verbose_failures',
            # Set an explicit Java language version
            'build --java_language_version=8',
            'build --tool_java_language_version=8',
            'build --lockfile_mode=update',
        ]
        + (
            [
                # Disable yanked version check so we are not affected BCR
                # changes.
                'build --allow_yanked_versions=all',
            ]
            if allow_yanked_versions
            else []
        ),
    )

  def testNonRegistryOverriddenModulesIgnoreYanked(self):
    self.writeBazelrcFile(allow_yanked_versions=False)
    src_yanked1 = self.main_registry.projects.joinpath('yanked1', '1.0')
    self.ScratchFile(
        'MODULE.bazel',
        [
            'bazel_dep(name = "yanked1", version = "1.0")',
            'local_path_override(',
            '  module_name = "yanked1",',
            '  path = "%s",' % str(src_yanked1.resolve()).replace('\\', '/'),
            ')',
        ],
    )
    self.ScratchFile('WORKSPACE')
    self.ScratchFile(
        'BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["@yanked1//:lib_yanked1"],',
            ')',
        ],
    )
    self.RunBazel(['build', '--nobuild', '//:main'])

  def testContainingYankedDepFails(self):
    self.writeBazelrcFile(allow_yanked_versions=False)
    self.ScratchFile(
        'MODULE.bazel',
        [
            'bazel_dep(name = "yanked1", version = "1.0")',
        ],
    )
    self.ScratchFile('WORKSPACE')
    self.ScratchFile(
        'BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["@ddd//:lib_ddd"],',
            ')',
        ],
    )
    exit_code, _, stderr = self.RunBazel(
        ['build', '--nobuild', '//:main'], allow_failure=True
    )
    self.AssertExitCode(exit_code, 48, stderr)
    self.assertIn(
        'Yanked version detected in your resolved dependency graph: '
        + 'yanked1@1.0, for the reason: dodgy.',
        ''.join(stderr),
    )

  def testAllowedYankedDepsSuccessByFlag(self):
    self.writeBazelrcFile(allow_yanked_versions=False)
    self.ScratchFile(
        'MODULE.bazel',
        [
            'bazel_dep(name = "ddd", version = "1.0")',
        ],
    )
    self.ScratchFile('WORKSPACE')
    self.ScratchFile(
        'BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["@ddd//:lib_ddd"],',
            ')',
        ],
    )
    self.RunBazel(
        [
            'build',
            '--nobuild',
            '--allow_yanked_versions=yanked1@1.0,yanked2@1.0',
            '//:main',
        ],
    )

  def testAllowedYankedDepsByEnvVar(self):
    self.writeBazelrcFile(allow_yanked_versions=False)
    self.ScratchFile(
        'MODULE.bazel',
        [
            'bazel_dep(name = "ddd", version = "1.0")',
        ],
    )
    self.ScratchFile('WORKSPACE')
    self.ScratchFile(
        'BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["@ddd//:lib_ddd"],',
            ')',
        ],
    )
    self.RunBazel(
        ['build', '--nobuild', '//:main'],
        env_add={'BZLMOD_ALLOW_YANKED_VERSIONS': 'yanked1@1.0,yanked2@1.0'},
    )

    # Test changing the env var, the build should fail again.
    exit_code, _, stderr = self.RunBazel(
        ['build', '--nobuild', '//:main'],
        env_add={'BZLMOD_ALLOW_YANKED_VERSIONS': 'yanked2@1.0'},
        allow_failure=True,
    )
    self.AssertExitCode(exit_code, 48, stderr)
    self.assertIn(
        'Yanked version detected in your resolved dependency graph: '
        + 'yanked1@1.0, for the reason: dodgy.',
        ''.join(stderr),
    )

  def testAllowedYankedDepsSuccessMix(self):
    self.writeBazelrcFile(allow_yanked_versions=False)
    self.ScratchFile(
        'MODULE.bazel',
        [
            'bazel_dep(name = "ddd", version = "1.0")',
        ],
    )
    self.ScratchFile('WORKSPACE')
    self.ScratchFile(
        'BUILD',
        [
            'cc_binary(',
            '  name = "main",',
            '  srcs = ["main.cc"],',
            '  deps = ["@ddd//:lib_ddd"],',
            ')',
        ],
    )
    self.RunBazel(
        [
            'build',
            '--nobuild',
            '--allow_yanked_versions=yanked1@1.0',
            '//:main',
        ],
        env_add={'BZLMOD_ALLOW_YANKED_VERSIONS': 'yanked2@1.0'},
    )


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