# Copyright 2023 The Pigweed Authors
#
# 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
#
#     https://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 the pw_build.gn_target module."""

import unittest

from pw_build.bazel_query import BazelRule
from pw_build.gn_target import GnTarget
from pw_build.gn_utils import GnLabel, MalformedGnError


class TestGnTarget(unittest.TestCase):
    """Tests for gn_target.GnTarget."""

    def setUp(self):
        self.rule = BazelRule('//my-package:my-target', 'cc_library')

    def test_from_bazel_rule_label(self):
        """Tests the GN target name and package derived from a Bazel rule."""
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(target.name(), 'my-target')
        self.assertEqual(target.package(), 'my-package')

    def test_from_bazel_rule_type_source_set(self):
        """Tests creating a `pw_source_set` from a Bazel rule."""
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(target.type(), 'pw_source_set')

    def test_from_bazel_rule_type_static_lib(self):
        """Tests creating a `pw_static_library` from a Bazel rule."""
        self.rule.set_attr('linkstatic', True)
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(target.type(), 'pw_static_library')

    def test_from_bazel_rule_type_invalid(self):
        """Tests creating an invalid type from a Bazel rule."""
        with self.assertRaises(MalformedGnError):
            rule = BazelRule('//my-package:my-target', 'custom_type')
            GnTarget('$build', '$src', bazel=rule)

    def test_from_bazel_rule_visibility(self):
        """Tests getting the GN visibility from a Bazel rule."""
        self.rule.set_attr(
            'visibility',
            [
                '//foo:__subpackages__',
                '//foo/bar:__pkg__',
                '//baz:__pkg__',
            ],
        )
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            {str(scope) for scope in target.visibility},
            {
                '$build/my-package:*',
                '$build/foo/*',
                '$build/baz:*',
            },
        )

    def test_from_bazel_rule_visibility_public(self):
        """Tests that 'public' overrides any other visibility"""
        self.rule.set_attr(
            'visibility',
            [
                '//visibility:private',
                '//visibility:public',
            ],
        )
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual({str(scope) for scope in target.visibility}, {'//*'})

    def test_from_bazel_rule_visibility_invalid(self):
        """Tests that and invalid visibility raises an error."""
        self.rule.set_attr('visibility', ['invalid'])
        with self.assertRaises(MalformedGnError):
            GnTarget('$build', '$src', bazel=self.rule)

    def test_from_bazel_rule_testonly_unset(self):
        """Tests that omitting `testonly` defaults it to False."""
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertFalse(target.testonly)

    def test_from_bazel_rule_testonly_false(self):
        """Tests setting `testonly` to False."""
        self.rule.set_attr('testonly', False)
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertFalse(target.testonly)

    def test_from_bazel_rule_testonly_true(self):
        """Tests setting `testonly` to True."""
        self.rule.set_attr('testonly', True)
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertTrue(target.testonly)

    def test_from_bazel_rule_public(self):
        """Tests getting the GN public headers from a Bazel rule."""
        self.rule.set_attr('hdrs', ['//foo:bar.h', '//:baz.h'])
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            {str(path) for path in target.public},
            {
                '$src/foo/bar.h',
                '$src/baz.h',
            },
        )

    def test_from_bazel_rule_sources(self):
        """Tests getting the GN source files from a Bazel rule."""
        self.rule.set_attr('srcs', ['//foo:bar.cc', '//:baz.cc'])
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            {str(path) for path in target.sources},
            {
                '$src/foo/bar.cc',
                '$src/baz.cc',
            },
        )

    def test_from_bazel_rule_inputs(self):
        """Tests getting the GN input files from a Bazel rule."""
        self.rule.set_attr(
            'additional_linker_inputs', ['//foo:bar.data', '//:baz.data']
        )
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            {str(path) for path in target.inputs},
            {
                '$src/foo/bar.data',
                '$src/baz.data',
            },
        )

    def test_from_bazel_rule_include_dirs(self):
        """Tests getting the GN include directories from a Bazel rule."""
        self.rule.set_attr('includes', ['//foo'])
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            set(target.config.get('include_dirs')),
            {
                '$src',
                '$src/foo',
            },
        )

    def test_from_bazel_rule_configs(self):
        """Tests getting the GN config flags from a Bazel rule."""
        self.rule.set_attr('defines', ['KEY1=VAL1'])
        self.rule.set_attr('copts', ['-frobinator'])
        self.rule.set_attr('linkopts', ['-fizzbuzzer'])
        self.rule.set_attr('local_defines', ['KEY2=VAL2', 'KEY3=VAL3'])
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            set(target.config.get('public_defines')), {'KEY1=VAL1'}
        )
        self.assertEqual(set(target.config.get('cflags')), {'-frobinator'})
        self.assertEqual(set(target.config.get('ldflags')), {'-fizzbuzzer'})
        self.assertEqual(
            set(target.config.get('defines')), {'KEY2=VAL2', 'KEY3=VAL3'}
        )

    def test_from_bazel_rule_deps(self):
        """Tests getting the GN dependencies from a Bazel rule."""
        self.rule.set_attr(
            'deps', ['//my-package:foo', '@com_corp_project//bar']
        )
        self.rule.set_attr(
            'implementation_deps',
            [
                '//other-pkg/subdir',
                '@com_corp_project//:top-level',
            ],
        )
        target = GnTarget('$build', '$src', bazel=self.rule)
        self.assertEqual(
            {str(label) for label in target.public_deps},
            {
                '$build/my-package:foo',
                '$repo/bar',
            },
        )
        self.assertEqual(
            {str(label) for label in target.deps},
            {
                '$build/other-pkg/subdir',
                '$repo:top-level',
            },
        )

    def test_make_relative_adjacent(self):
        """Tests rebasing labels for a target."""
        target = GnTarget('$build/pkg', '$src')
        label = target.make_relative(GnLabel('$build/pkg:adjacent-config'))
        self.assertEqual(label, ":adjacent-config")

    def test_make_relative_absolute(self):
        """Tests rebasing labels for a target."""
        target = GnTarget('$build/pkg', '$src')
        label = target.make_relative(GnLabel('//absolute:config'))
        self.assertEqual(label, "//absolute:config")

    def test_make_relative_descend(self):
        """Tests rebasing labels for a target."""
        target = GnTarget('$build/pkg', '$src')
        label = target.make_relative(GnLabel('$build/pkg/relative:dep'))
        self.assertEqual(label, "relative:dep")

    def test_make_relative_ascend(self):
        """Tests rebasing labels for a target."""
        target = GnTarget('$build/pkg', '$src')
        label = target.make_relative(GnLabel('$build/dotdot/relative'))
        self.assertEqual(label, "../dotdot/relative")


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