# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import json
import shutil
import unittest
from pathlib import Path
from unittest.mock import MagicMock, call, patch

from .. import source_database_buck_builder


class SourceDatabaseBuckBuilderTest(unittest.TestCase):
    def setUp(self) -> None:
        self._query_arguments = [
            "query",
            "--json",
            'kind("python_binary|python_library|python_test", %s) '
            "- attrfilter(labels, generated, %s) "
            "+ attrfilter(labels, unittest-library, %s) "
            "- attrfilter(labels, no_pyre, %s)",
            "//foo/bar/...",
            "//bar:baz",
        ]

    def test_get_buck_query_arguments(self) -> None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=["//foo/bar/...", "//bar:baz"], mode=None
        )
        self.assertEqual(arguments, self._query_arguments)

    def test_get_buck_query_arguments__with_mode(self) -> None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=["//foo/bar/...", "//bar:baz"], mode="foo"
        )
        self.assertEqual(
            arguments,
            [
                "query",
                "--json",
                "@mode/foo",
                'kind("python_binary|python_library|python_test", %s) '
                "- attrfilter(labels, generated, %s) "
                "+ attrfilter(labels, unittest-library, %s) "
                "- attrfilter(labels, no_pyre, %s)",
                "//foo/bar/...",
                "//bar:baz",
            ],
        )

    # pyre-fixme[56]: Pyre was not able to infer the type of argument
    #  `tools.pyre.client.source_database_buck_builder` to decorator factory
    #  `unittest.mock.patch.object`.
    @patch.object(source_database_buck_builder, "_buck")
    def test_query_targets(self, buck: MagicMock) -> None:
        query_output = {
            "//foo/bar/...": ["//foo/bar:baz", "//foo/bar:tests-library"],
            "//bar:baz": [
                "//bar:baz",
                "//bar:tests-mypy_ini",
                "//bar:tests-library-testmodules-lib",
            ],
        }
        buck.return_value = json.dumps(query_output)

        self.assertEqual(
            source_database_buck_builder._query_targets(
                ["//foo/bar/...", "//bar:baz"],
                isolation_prefix=None,
                mode=None,
                buck_root=Path(""),
            ),
            ["//foo/bar:baz", "//foo/bar:tests-library", "//bar:baz"],
        )

    def test_buck_build_arguments(self) -> None:
        self.assertEqual(
            source_database_buck_builder._get_buck_build_arguments(
                mode="opt", targets=["//foo/bar:baz", "//foo/bar:tests-library"]
            ),
            [
                "@mode/opt",
                "--show-full-json-output",
                "//foo/bar:baz#source-db",
                "//foo/bar:tests-library#source-db",
            ],
        )

    # pyre-fixme[56]: Argument `json` to decorator factory
    # `unittest.mock.patch.object` could not be resolved in a global scope.
    @patch.object(json, "loads")
    @patch.object(Path, "read_text")
    def test_load_source_databases(
        self, read_text: MagicMock, loads: MagicMock
    ) -> None:
        expected_database = {
            "sources": {"bar.py": "some/other/bar.py"},
            "dependencies": {"foo.py": "some/foo.py"},
        }
        loads.return_value = expected_database
        source_databases = source_database_buck_builder._load_source_databases(
            {"//foo:bar#source-db": "/some/bar#source-db/db.json"}
        )
        self.assertEqual(source_databases, {"//foo:bar#source-db": expected_database})

    def test_merge_source_databases(self) -> None:
        actual = source_database_buck_builder._merge_source_databases(
            {
                "hello": {
                    "sources": {
                        "foo.py": "foo.py",
                        "duplicate.py": "duplicate_in_hello.py",
                    },
                    "dependencies": {
                        "bar.pyi": "buck-out/bar.pyi",
                        "bar.cpp": "bar.cpp",
                    },
                },
                "foo": {
                    "sources": {},
                    "dependencies": {
                        "foo2.pyi": "buck-out/foo2.pyi",
                        "bar2.cpp": "bar2.cpp",
                        "duplicate.py": "duplicate_in_foo.py",
                        "__manifest__.py": "__manifest__.py",
                        "__test_modules__.py": "__test_modules__.py",
                        "__test_main__.py": "__test_main__.py",
                    },
                },
            }
        )
        self.assertEqual(
            actual,
            {
                "foo.py": "foo.py",
                "duplicate.py": "duplicate_in_foo.py",
                "bar.pyi": "buck-out/bar.pyi",
                "foo2.pyi": "buck-out/foo2.pyi",
            },
        )

    # pyre-fixme[56]: Argument `shutil` to decorator factory
    # `unittest.mock.patch.object` could not be resolved in a global scope.
    @patch.object(shutil, "rmtree")
    @patch.object(Path, "exists")
    @patch.object(Path, "mkdir")
    @patch.object(Path, "symlink_to")
    def test_build_link_tree(
        self,
        symlink_to: MagicMock,
        make_directory: MagicMock,
        exists: MagicMock,
        remove_tree: MagicMock,
    ) -> None:
        source_database_buck_builder._build_link_tree(
            {"foo.py": "foo.py", "bar/baz.pyi": "buck-out/bar.pyi"},
            Path("foo_directory"),
            Path("/root"),
        )
        self.assertEqual(
            make_directory.call_args_list,
            [
                call(parents=True),
                call(parents=True, exist_ok=True),
                call(parents=True, exist_ok=True),
            ],
        )
        self.assertEqual(
            symlink_to.call_args_list,
            [call(Path("/root/foo.py")), call(Path("/root/buck-out/bar.pyi"))],
        )

    @patch.object(source_database_buck_builder, "_build_link_tree")
    @patch.object(source_database_buck_builder, "_load_source_databases")
    @patch.object(source_database_buck_builder, "_build_targets")
    # pyre-fixme[56]: Argument
    #  `tools.pyre.tools.buck_project_builder.source_database_buck_builder` to
    #  decorator factory `unittest.mock.patch.object` could not be resolved in a global
    #  scope.
    @patch.object(source_database_buck_builder, "_query_targets")
    def test_build(
        self,
        query_targets: MagicMock,
        build_targets: MagicMock,
        load_source_databases: MagicMock,
        build_link_tree: MagicMock,
    ) -> None:
        load_source_databases.return_value = {
            "hello": {"sources": {"foo.py": "foo.py"}, "dependencies": {}},
            "foo": {"sources": {}, "dependencies": {"bar.pyi": "buck-out/bar.pyi"}},
        }
        source_database_buck_builder.build(
            ["//foo/bar/..."],
            output_directory=Path("output_directory"),
            buck_root=Path("buck_root"),
            isolation_prefix=None,
            mode=None,
        )
        query_targets.assert_called_once()
        build_targets.assert_called_once()
        build_link_tree.assert_called_once_with(
            {"foo.py": "foo.py", "bar.pyi": "buck-out/bar.pyi"},
            Path("output_directory"),
            Path("buck_root"),
        )

    def test_normalize_specification(self) -> None:
        self.assertEqual(
            source_database_buck_builder._normalize_specification("foo/bar:baz"),
            "//foo/bar:baz",
        )
        self.assertEqual(
            source_database_buck_builder._normalize_specification(
                "some_root//foo/bar:baz"
            ),
            "some_root//foo/bar:baz",
        )

    def test_load_json__no_extra_data(self) -> None:
        self.assertEqual(
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                {
                    "a": "b",
                    "a2": "b2"
                }
                """
            ),
            {"a": "b", "a2": "b2"},
        )

    def test_load_json__extra_data(self) -> None:
        self.assertEqual(
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                {
                    "a": "b",
                    "a2": "b2"
                }
                Some error message.
                Some error message.
                """
            ),
            {"a": "b", "a2": "b2"},
        )

    def test_load_json__exception(self) -> None:
        with self.assertRaises(json.JSONDecodeError):
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                Malformed JSON.
                """
            )

021c16b3aa2b8424555798eccb39edf29e4d790f
<mask token>


class SourceDatabaseBuckBuilderTest(unittest.TestCase):
    <mask token>
    <mask token>

    def test_get_buck_query_arguments__with_mode(self) ->None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=['//foo/bar/...', '//bar:baz'], mode='foo')
        self.assertEqual(arguments, ['query', '--json', '@mode/foo',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz'])

    @patch.object(source_database_buck_builder, '_buck')
    def test_query_targets(self, buck: MagicMock) ->None:
        query_output = {'//foo/bar/...': ['//foo/bar:baz',
            '//foo/bar:tests-library'], '//bar:baz': ['//bar:baz',
            '//bar:tests-mypy_ini', '//bar:tests-library-testmodules-lib']}
        buck.return_value = json.dumps(query_output)
        self.assertEqual(source_database_buck_builder._query_targets([
            '//foo/bar/...', '//bar:baz'], isolation_prefix=None, mode=None,
            buck_root=Path('')), ['//foo/bar:baz',
            '//foo/bar:tests-library', '//bar:baz'])
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    @patch.object(source_database_buck_builder, '_build_link_tree')
    @patch.object(source_database_buck_builder, '_load_source_databases')
    @patch.object(source_database_buck_builder, '_build_targets')
    @patch.object(source_database_buck_builder, '_query_targets')
    def test_build(self, query_targets: MagicMock, build_targets: MagicMock,
        load_source_databases: MagicMock, build_link_tree: MagicMock) ->None:
        load_source_databases.return_value = {'hello': {'sources': {
            'foo.py': 'foo.py'}, 'dependencies': {}}, 'foo': {'sources': {},
            'dependencies': {'bar.pyi': 'buck-out/bar.pyi'}}}
        source_database_buck_builder.build(['//foo/bar/...'],
            output_directory=Path('output_directory'), buck_root=Path(
            'buck_root'), isolation_prefix=None, mode=None)
        query_targets.assert_called_once()
        build_targets.assert_called_once()
        build_link_tree.assert_called_once_with({'foo.py': 'foo.py',
            'bar.pyi': 'buck-out/bar.pyi'}, Path('output_directory'), Path(
            'buck_root'))
    <mask token>

    def test_load_json__no_extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                Some error message.
                Some error message.
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__exception(self) ->None:
        with self.assertRaises(json.JSONDecodeError):
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                Malformed JSON.
                """)

<mask token>


class SourceDatabaseBuckBuilderTest(unittest.TestCase):

    def setUp(self) ->None:
        self._query_arguments = ['query', '--json',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz']
    <mask token>

    def test_get_buck_query_arguments__with_mode(self) ->None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=['//foo/bar/...', '//bar:baz'], mode='foo')
        self.assertEqual(arguments, ['query', '--json', '@mode/foo',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz'])

    @patch.object(source_database_buck_builder, '_buck')
    def test_query_targets(self, buck: MagicMock) ->None:
        query_output = {'//foo/bar/...': ['//foo/bar:baz',
            '//foo/bar:tests-library'], '//bar:baz': ['//bar:baz',
            '//bar:tests-mypy_ini', '//bar:tests-library-testmodules-lib']}
        buck.return_value = json.dumps(query_output)
        self.assertEqual(source_database_buck_builder._query_targets([
            '//foo/bar/...', '//bar:baz'], isolation_prefix=None, mode=None,
            buck_root=Path('')), ['//foo/bar:baz',
            '//foo/bar:tests-library', '//bar:baz'])
    <mask token>
    <mask token>
    <mask token>
    <mask token>

    @patch.object(source_database_buck_builder, '_build_link_tree')
    @patch.object(source_database_buck_builder, '_load_source_databases')
    @patch.object(source_database_buck_builder, '_build_targets')
    @patch.object(source_database_buck_builder, '_query_targets')
    def test_build(self, query_targets: MagicMock, build_targets: MagicMock,
        load_source_databases: MagicMock, build_link_tree: MagicMock) ->None:
        load_source_databases.return_value = {'hello': {'sources': {
            'foo.py': 'foo.py'}, 'dependencies': {}}, 'foo': {'sources': {},
            'dependencies': {'bar.pyi': 'buck-out/bar.pyi'}}}
        source_database_buck_builder.build(['//foo/bar/...'],
            output_directory=Path('output_directory'), buck_root=Path(
            'buck_root'), isolation_prefix=None, mode=None)
        query_targets.assert_called_once()
        build_targets.assert_called_once()
        build_link_tree.assert_called_once_with({'foo.py': 'foo.py',
            'bar.pyi': 'buck-out/bar.pyi'}, Path('output_directory'), Path(
            'buck_root'))
    <mask token>

    def test_load_json__no_extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                Some error message.
                Some error message.
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__exception(self) ->None:
        with self.assertRaises(json.JSONDecodeError):
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                Malformed JSON.
                """)

<mask token>


class SourceDatabaseBuckBuilderTest(unittest.TestCase):

    def setUp(self) ->None:
        self._query_arguments = ['query', '--json',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz']
    <mask token>

    def test_get_buck_query_arguments__with_mode(self) ->None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=['//foo/bar/...', '//bar:baz'], mode='foo')
        self.assertEqual(arguments, ['query', '--json', '@mode/foo',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz'])

    @patch.object(source_database_buck_builder, '_buck')
    def test_query_targets(self, buck: MagicMock) ->None:
        query_output = {'//foo/bar/...': ['//foo/bar:baz',
            '//foo/bar:tests-library'], '//bar:baz': ['//bar:baz',
            '//bar:tests-mypy_ini', '//bar:tests-library-testmodules-lib']}
        buck.return_value = json.dumps(query_output)
        self.assertEqual(source_database_buck_builder._query_targets([
            '//foo/bar/...', '//bar:baz'], isolation_prefix=None, mode=None,
            buck_root=Path('')), ['//foo/bar:baz',
            '//foo/bar:tests-library', '//bar:baz'])

    def test_buck_build_arguments(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _get_buck_build_arguments(mode='opt', targets=['//foo/bar:baz',
            '//foo/bar:tests-library']), ['@mode/opt',
            '--show-full-json-output', '//foo/bar:baz#source-db',
            '//foo/bar:tests-library#source-db'])
    <mask token>

    def test_merge_source_databases(self) ->None:
        actual = source_database_buck_builder._merge_source_databases({
            'hello': {'sources': {'foo.py': 'foo.py', 'duplicate.py':
            'duplicate_in_hello.py'}, 'dependencies': {'bar.pyi':
            'buck-out/bar.pyi', 'bar.cpp': 'bar.cpp'}}, 'foo': {'sources':
            {}, 'dependencies': {'foo2.pyi': 'buck-out/foo2.pyi',
            'bar2.cpp': 'bar2.cpp', 'duplicate.py': 'duplicate_in_foo.py',
            '__manifest__.py': '__manifest__.py', '__test_modules__.py':
            '__test_modules__.py', '__test_main__.py': '__test_main__.py'}}})
        self.assertEqual(actual, {'foo.py': 'foo.py', 'duplicate.py':
            'duplicate_in_foo.py', 'bar.pyi': 'buck-out/bar.pyi',
            'foo2.pyi': 'buck-out/foo2.pyi'})

    @patch.object(shutil, 'rmtree')
    @patch.object(Path, 'exists')
    @patch.object(Path, 'mkdir')
    @patch.object(Path, 'symlink_to')
    def test_build_link_tree(self, symlink_to: MagicMock, make_directory:
        MagicMock, exists: MagicMock, remove_tree: MagicMock) ->None:
        source_database_buck_builder._build_link_tree({'foo.py': 'foo.py',
            'bar/baz.pyi': 'buck-out/bar.pyi'}, Path('foo_directory'), Path
            ('/root'))
        self.assertEqual(make_directory.call_args_list, [call(parents=True),
            call(parents=True, exist_ok=True), call(parents=True, exist_ok=
            True)])
        self.assertEqual(symlink_to.call_args_list, [call(Path(
            '/root/foo.py')), call(Path('/root/buck-out/bar.pyi'))])

    @patch.object(source_database_buck_builder, '_build_link_tree')
    @patch.object(source_database_buck_builder, '_load_source_databases')
    @patch.object(source_database_buck_builder, '_build_targets')
    @patch.object(source_database_buck_builder, '_query_targets')
    def test_build(self, query_targets: MagicMock, build_targets: MagicMock,
        load_source_databases: MagicMock, build_link_tree: MagicMock) ->None:
        load_source_databases.return_value = {'hello': {'sources': {
            'foo.py': 'foo.py'}, 'dependencies': {}}, 'foo': {'sources': {},
            'dependencies': {'bar.pyi': 'buck-out/bar.pyi'}}}
        source_database_buck_builder.build(['//foo/bar/...'],
            output_directory=Path('output_directory'), buck_root=Path(
            'buck_root'), isolation_prefix=None, mode=None)
        query_targets.assert_called_once()
        build_targets.assert_called_once()
        build_link_tree.assert_called_once_with({'foo.py': 'foo.py',
            'bar.pyi': 'buck-out/bar.pyi'}, Path('output_directory'), Path(
            'buck_root'))

    def test_normalize_specification(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _normalize_specification('foo/bar:baz'), '//foo/bar:baz')
        self.assertEqual(source_database_buck_builder.
            _normalize_specification('some_root//foo/bar:baz'),
            'some_root//foo/bar:baz')

    def test_load_json__no_extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                Some error message.
                Some error message.
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__exception(self) ->None:
        with self.assertRaises(json.JSONDecodeError):
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                Malformed JSON.
                """)

<mask token>


class SourceDatabaseBuckBuilderTest(unittest.TestCase):

    def setUp(self) ->None:
        self._query_arguments = ['query', '--json',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz']

    def test_get_buck_query_arguments(self) ->None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=['//foo/bar/...', '//bar:baz'], mode=None)
        self.assertEqual(arguments, self._query_arguments)

    def test_get_buck_query_arguments__with_mode(self) ->None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=['//foo/bar/...', '//bar:baz'], mode='foo')
        self.assertEqual(arguments, ['query', '--json', '@mode/foo',
            'kind("python_binary|python_library|python_test", %s) - attrfilter(labels, generated, %s) + attrfilter(labels, unittest-library, %s) - attrfilter(labels, no_pyre, %s)'
            , '//foo/bar/...', '//bar:baz'])

    @patch.object(source_database_buck_builder, '_buck')
    def test_query_targets(self, buck: MagicMock) ->None:
        query_output = {'//foo/bar/...': ['//foo/bar:baz',
            '//foo/bar:tests-library'], '//bar:baz': ['//bar:baz',
            '//bar:tests-mypy_ini', '//bar:tests-library-testmodules-lib']}
        buck.return_value = json.dumps(query_output)
        self.assertEqual(source_database_buck_builder._query_targets([
            '//foo/bar/...', '//bar:baz'], isolation_prefix=None, mode=None,
            buck_root=Path('')), ['//foo/bar:baz',
            '//foo/bar:tests-library', '//bar:baz'])

    def test_buck_build_arguments(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _get_buck_build_arguments(mode='opt', targets=['//foo/bar:baz',
            '//foo/bar:tests-library']), ['@mode/opt',
            '--show-full-json-output', '//foo/bar:baz#source-db',
            '//foo/bar:tests-library#source-db'])

    @patch.object(json, 'loads')
    @patch.object(Path, 'read_text')
    def test_load_source_databases(self, read_text: MagicMock, loads: MagicMock
        ) ->None:
        expected_database = {'sources': {'bar.py': 'some/other/bar.py'},
            'dependencies': {'foo.py': 'some/foo.py'}}
        loads.return_value = expected_database
        source_databases = source_database_buck_builder._load_source_databases(
            {'//foo:bar#source-db': '/some/bar#source-db/db.json'})
        self.assertEqual(source_databases, {'//foo:bar#source-db':
            expected_database})

    def test_merge_source_databases(self) ->None:
        actual = source_database_buck_builder._merge_source_databases({
            'hello': {'sources': {'foo.py': 'foo.py', 'duplicate.py':
            'duplicate_in_hello.py'}, 'dependencies': {'bar.pyi':
            'buck-out/bar.pyi', 'bar.cpp': 'bar.cpp'}}, 'foo': {'sources':
            {}, 'dependencies': {'foo2.pyi': 'buck-out/foo2.pyi',
            'bar2.cpp': 'bar2.cpp', 'duplicate.py': 'duplicate_in_foo.py',
            '__manifest__.py': '__manifest__.py', '__test_modules__.py':
            '__test_modules__.py', '__test_main__.py': '__test_main__.py'}}})
        self.assertEqual(actual, {'foo.py': 'foo.py', 'duplicate.py':
            'duplicate_in_foo.py', 'bar.pyi': 'buck-out/bar.pyi',
            'foo2.pyi': 'buck-out/foo2.pyi'})

    @patch.object(shutil, 'rmtree')
    @patch.object(Path, 'exists')
    @patch.object(Path, 'mkdir')
    @patch.object(Path, 'symlink_to')
    def test_build_link_tree(self, symlink_to: MagicMock, make_directory:
        MagicMock, exists: MagicMock, remove_tree: MagicMock) ->None:
        source_database_buck_builder._build_link_tree({'foo.py': 'foo.py',
            'bar/baz.pyi': 'buck-out/bar.pyi'}, Path('foo_directory'), Path
            ('/root'))
        self.assertEqual(make_directory.call_args_list, [call(parents=True),
            call(parents=True, exist_ok=True), call(parents=True, exist_ok=
            True)])
        self.assertEqual(symlink_to.call_args_list, [call(Path(
            '/root/foo.py')), call(Path('/root/buck-out/bar.pyi'))])

    @patch.object(source_database_buck_builder, '_build_link_tree')
    @patch.object(source_database_buck_builder, '_load_source_databases')
    @patch.object(source_database_buck_builder, '_build_targets')
    @patch.object(source_database_buck_builder, '_query_targets')
    def test_build(self, query_targets: MagicMock, build_targets: MagicMock,
        load_source_databases: MagicMock, build_link_tree: MagicMock) ->None:
        load_source_databases.return_value = {'hello': {'sources': {
            'foo.py': 'foo.py'}, 'dependencies': {}}, 'foo': {'sources': {},
            'dependencies': {'bar.pyi': 'buck-out/bar.pyi'}}}
        source_database_buck_builder.build(['//foo/bar/...'],
            output_directory=Path('output_directory'), buck_root=Path(
            'buck_root'), isolation_prefix=None, mode=None)
        query_targets.assert_called_once()
        build_targets.assert_called_once()
        build_link_tree.assert_called_once_with({'foo.py': 'foo.py',
            'bar.pyi': 'buck-out/bar.pyi'}, Path('output_directory'), Path(
            'buck_root'))

    def test_normalize_specification(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _normalize_specification('foo/bar:baz'), '//foo/bar:baz')
        self.assertEqual(source_database_buck_builder.
            _normalize_specification('some_root//foo/bar:baz'),
            'some_root//foo/bar:baz')

    def test_load_json__no_extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__extra_data(self) ->None:
        self.assertEqual(source_database_buck_builder.
            _load_json_ignoring_extra_data(
            """
                {
                    "a": "b",
                    "a2": "b2"
                }
                Some error message.
                Some error message.
                """
            ), {'a': 'b', 'a2': 'b2'})

    def test_load_json__exception(self) ->None:
        with self.assertRaises(json.JSONDecodeError):
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                Malformed JSON.
                """)

# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import json
import shutil
import unittest
from pathlib import Path
from unittest.mock import MagicMock, call, patch

from .. import source_database_buck_builder


class SourceDatabaseBuckBuilderTest(unittest.TestCase):
    def setUp(self) -> None:
        self._query_arguments = [
            "query",
            "--json",
            'kind("python_binary|python_library|python_test", %s) '
            "- attrfilter(labels, generated, %s) "
            "+ attrfilter(labels, unittest-library, %s) "
            "- attrfilter(labels, no_pyre, %s)",
            "//foo/bar/...",
            "//bar:baz",
        ]

    def test_get_buck_query_arguments(self) -> None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=["//foo/bar/...", "//bar:baz"], mode=None
        )
        self.assertEqual(arguments, self._query_arguments)

    def test_get_buck_query_arguments__with_mode(self) -> None:
        arguments = source_database_buck_builder._get_buck_query_arguments(
            specifications=["//foo/bar/...", "//bar:baz"], mode="foo"
        )
        self.assertEqual(
            arguments,
            [
                "query",
                "--json",
                "@mode/foo",
                'kind("python_binary|python_library|python_test", %s) '
                "- attrfilter(labels, generated, %s) "
                "+ attrfilter(labels, unittest-library, %s) "
                "- attrfilter(labels, no_pyre, %s)",
                "//foo/bar/...",
                "//bar:baz",
            ],
        )

    # pyre-fixme[56]: Pyre was not able to infer the type of argument
    #  `tools.pyre.client.source_database_buck_builder` to decorator factory
    #  `unittest.mock.patch.object`.
    @patch.object(source_database_buck_builder, "_buck")
    def test_query_targets(self, buck: MagicMock) -> None:
        query_output = {
            "//foo/bar/...": ["//foo/bar:baz", "//foo/bar:tests-library"],
            "//bar:baz": [
                "//bar:baz",
                "//bar:tests-mypy_ini",
                "//bar:tests-library-testmodules-lib",
            ],
        }
        buck.return_value = json.dumps(query_output)

        self.assertEqual(
            source_database_buck_builder._query_targets(
                ["//foo/bar/...", "//bar:baz"],
                isolation_prefix=None,
                mode=None,
                buck_root=Path(""),
            ),
            ["//foo/bar:baz", "//foo/bar:tests-library", "//bar:baz"],
        )

    def test_buck_build_arguments(self) -> None:
        self.assertEqual(
            source_database_buck_builder._get_buck_build_arguments(
                mode="opt", targets=["//foo/bar:baz", "//foo/bar:tests-library"]
            ),
            [
                "@mode/opt",
                "--show-full-json-output",
                "//foo/bar:baz#source-db",
                "//foo/bar:tests-library#source-db",
            ],
        )

    # pyre-fixme[56]: Argument `json` to decorator factory
    # `unittest.mock.patch.object` could not be resolved in a global scope.
    @patch.object(json, "loads")
    @patch.object(Path, "read_text")
    def test_load_source_databases(
        self, read_text: MagicMock, loads: MagicMock
    ) -> None:
        expected_database = {
            "sources": {"bar.py": "some/other/bar.py"},
            "dependencies": {"foo.py": "some/foo.py"},
        }
        loads.return_value = expected_database
        source_databases = source_database_buck_builder._load_source_databases(
            {"//foo:bar#source-db": "/some/bar#source-db/db.json"}
        )
        self.assertEqual(source_databases, {"//foo:bar#source-db": expected_database})

    def test_merge_source_databases(self) -> None:
        actual = source_database_buck_builder._merge_source_databases(
            {
                "hello": {
                    "sources": {
                        "foo.py": "foo.py",
                        "duplicate.py": "duplicate_in_hello.py",
                    },
                    "dependencies": {
                        "bar.pyi": "buck-out/bar.pyi",
                        "bar.cpp": "bar.cpp",
                    },
                },
                "foo": {
                    "sources": {},
                    "dependencies": {
                        "foo2.pyi": "buck-out/foo2.pyi",
                        "bar2.cpp": "bar2.cpp",
                        "duplicate.py": "duplicate_in_foo.py",
                        "__manifest__.py": "__manifest__.py",
                        "__test_modules__.py": "__test_modules__.py",
                        "__test_main__.py": "__test_main__.py",
                    },
                },
            }
        )
        self.assertEqual(
            actual,
            {
                "foo.py": "foo.py",
                "duplicate.py": "duplicate_in_foo.py",
                "bar.pyi": "buck-out/bar.pyi",
                "foo2.pyi": "buck-out/foo2.pyi",
            },
        )

    # pyre-fixme[56]: Argument `shutil` to decorator factory
    # `unittest.mock.patch.object` could not be resolved in a global scope.
    @patch.object(shutil, "rmtree")
    @patch.object(Path, "exists")
    @patch.object(Path, "mkdir")
    @patch.object(Path, "symlink_to")
    def test_build_link_tree(
        self,
        symlink_to: MagicMock,
        make_directory: MagicMock,
        exists: MagicMock,
        remove_tree: MagicMock,
    ) -> None:
        source_database_buck_builder._build_link_tree(
            {"foo.py": "foo.py", "bar/baz.pyi": "buck-out/bar.pyi"},
            Path("foo_directory"),
            Path("/root"),
        )
        self.assertEqual(
            make_directory.call_args_list,
            [
                call(parents=True),
                call(parents=True, exist_ok=True),
                call(parents=True, exist_ok=True),
            ],
        )
        self.assertEqual(
            symlink_to.call_args_list,
            [call(Path("/root/foo.py")), call(Path("/root/buck-out/bar.pyi"))],
        )

    @patch.object(source_database_buck_builder, "_build_link_tree")
    @patch.object(source_database_buck_builder, "_load_source_databases")
    @patch.object(source_database_buck_builder, "_build_targets")
    # pyre-fixme[56]: Argument
    #  `tools.pyre.tools.buck_project_builder.source_database_buck_builder` to
    #  decorator factory `unittest.mock.patch.object` could not be resolved in a global
    #  scope.
    @patch.object(source_database_buck_builder, "_query_targets")
    def test_build(
        self,
        query_targets: MagicMock,
        build_targets: MagicMock,
        load_source_databases: MagicMock,
        build_link_tree: MagicMock,
    ) -> None:
        load_source_databases.return_value = {
            "hello": {"sources": {"foo.py": "foo.py"}, "dependencies": {}},
            "foo": {"sources": {}, "dependencies": {"bar.pyi": "buck-out/bar.pyi"}},
        }
        source_database_buck_builder.build(
            ["//foo/bar/..."],
            output_directory=Path("output_directory"),
            buck_root=Path("buck_root"),
            isolation_prefix=None,
            mode=None,
        )
        query_targets.assert_called_once()
        build_targets.assert_called_once()
        build_link_tree.assert_called_once_with(
            {"foo.py": "foo.py", "bar.pyi": "buck-out/bar.pyi"},
            Path("output_directory"),
            Path("buck_root"),
        )

    def test_normalize_specification(self) -> None:
        self.assertEqual(
            source_database_buck_builder._normalize_specification("foo/bar:baz"),
            "//foo/bar:baz",
        )
        self.assertEqual(
            source_database_buck_builder._normalize_specification(
                "some_root//foo/bar:baz"
            ),
            "some_root//foo/bar:baz",
        )

    def test_load_json__no_extra_data(self) -> None:
        self.assertEqual(
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                {
                    "a": "b",
                    "a2": "b2"
                }
                """
            ),
            {"a": "b", "a2": "b2"},
        )

    def test_load_json__extra_data(self) -> None:
        self.assertEqual(
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                {
                    "a": "b",
                    "a2": "b2"
                }
                Some error message.
                Some error message.
                """
            ),
            {"a": "b", "a2": "b2"},
        )

    def test_load_json__exception(self) -> None:
        with self.assertRaises(json.JSONDecodeError):
            source_database_buck_builder._load_json_ignoring_extra_data(
                """
                Malformed JSON.
                """
            )
