import unittest
import tempfile
import os
from unittest.mock import MagicMock, mock_open, patch
import json
import io
import buildreq
import config
import pypidata

bak_get_pypi_name = pypidata.get_pypi_name
def get_pypi_name_wrapper(name, miss=None):
    """Ignore missing packags in pypi"""
    return bak_get_pypi_name(name)


class TestBuildreq(unittest.TestCase):

    def setUp(self):
        """
        Test setup method to reset the buildreq module
        """
        self.reqs = buildreq.Requirements("")
        self.reqs.banned_buildreqs.add('bannedreq')

    def test_add_buildreq(self):
        """
        Test add_buildreq with unbanned new req. Follow up by asserting that
        trying to add the same req a second time results in a False return.
        """
        self.assertTrue(self.reqs.add_buildreq('testreq'))
        self.assertIn('testreq', self.reqs.buildreqs)
        self.assertFalse(self.reqs.add_buildreq('testreq'))

    def test_add_buildreq_banned(self):
        """
        Test add_buildreq with banned new req
        """
        self.assertFalse(self.reqs.add_buildreq('bannedreq'))
        self.assertNotIn('bannedreq', self.reqs.buildreqs)

    def test_ban_requires(self):
        """
        Test ban_requires with req already present in requires
        """
        self.reqs.requires[None] = set(['testreq'])
        self.reqs.ban_requires('testreq')
        self.assertNotIn('testreq', self.reqs.requires[None])

    def test_ban_requires_subpkg(self):
        """
        Test ban_requires on a subpkg with req already present in requires
        """
        self.reqs.requires['subpkg'] = set(['testreq'])
        self.reqs.ban_requires('testreq', subpkg='subpkg')
        self.assertNotIn('testreq', self.reqs.requires['subpkg'])

    def test_add_requires(self):
        """
        Test add_requires with unbanned new req already present in
        buildreqs but not yet present in requires
        """
        self.reqs.add_buildreq('testreq')
        self.assertTrue(self.reqs.add_requires('testreq', ['testreq']))
        self.assertIn('testreq', self.reqs.requires[None])

    def test_add_requires_subpkg(self):
        """
        Test add_requires on a subpkg with unbanned new req already present in
        buildreqs but not yet present in requires
        """
        self.reqs.add_buildreq('testreq')
        self.assertTrue(self.reqs.add_requires('testreq', ['testreq'], subpkg='subpkg'))
        self.assertIn('testreq', self.reqs.requires['subpkg'])

    def test_add_requires_not_in_buildreqs(self):
        """
        Test add_requires with unbanned new req not present in buildreqs.
        """
        self.assertFalse(self.reqs.add_requires('testreq', []))
        self.assertNotIn('testreq', self.reqs.requires[None])

    def test_add_banned_requires(self):
        """
        Test add_requires with banned new req (override buildreq).
        """
        self.assertFalse(self.reqs.add_requires('pypi(nose)', [], override=True))
        self.assertNotIn('testreq', self.reqs.requires[None])

    def test_ban_provides(self):
        """
        Test ban_provides with prov already present in provides
        """
        self.reqs.provides[None] = set(['testreq'])
        self.reqs.ban_provides('testreq')
        self.assertNotIn('testreq', self.reqs.provides[None])

    def test_ban_provides_subpkg(self):
        """
        Test ban_provides on a subpkg with prov already present in provides
        """
        self.reqs.provides['subpkg'] = set(['testreq'])
        self.reqs.ban_provides('testreq', subpkg='subpkg')
        self.assertNotIn('testreq', self.reqs.provides['subpkg'])

    def test_add_provides(self):
        """
        Test add_provides with unbanned new prov
        """
        self.assertTrue(self.reqs.add_provides('testreq'))
        self.assertIn('testreq', self.reqs.provides[None])

    def test_add_provides_subpkg(self):
        """
        Test add_provides on a subpkg with unbanned new prov
        """
        self.assertTrue(self.reqs.add_provides('testreq', subpkg='subpkg'))
        self.assertIn('testreq', self.reqs.provides['subpkg'])

    def test_add_pkgconfig_buildreq(self):
        """
        Test add_pkgconfig_buildreq with config_opts['32bit'] set to False
        """
        self.assertTrue(self.reqs.add_pkgconfig_buildreq('testreq', False))
        self.assertIn('pkgconfig(testreq)', self.reqs.buildreqs)

    def test_add_pkgconfig_buildreq_32bit(self):
        """
        Test add_pkgconfig_buildreq with config_opts['32bit'] set to True
        """
        self.assertTrue(self.reqs.add_pkgconfig_buildreq('testreq', True))
        self.assertIn('pkgconfig(testreq)', self.reqs.buildreqs)
        self.assertIn('pkgconfig(32testreq)', self.reqs.buildreqs)

    def test_configure_ac_line(self):
        """
        Test configure_ac_line with standard pattern
        """
        self.reqs.configure_ac_line('AC_CHECK_FUNC([tgetent])', False)
        self.assertIn('ncurses-devel', self.reqs.buildreqs)

    def test_configure_ac_line_comment(self):
        """
        Test configure_ac_line with commented line
        """
        self.reqs.configure_ac_line('# AC_CHECK_FUNC([tgetent])', False)
        self.assertEqual(self.reqs.buildreqs, set())

    def test_configure_ac_line_pkg_check_modules(self):
        """
        Test the somewhat complicated logic of configure_ac_line check for the
        PKG_CHECK_MODULES((.*?)) line.
        """
        self.reqs.configure_ac_line(
            'PKG_CHECK_MODULES(prefix, '
            '[module > 2 module2 < 2], '
            'action-if-found, action-if-not-found)', False)
        self.assertEqual(self.reqs.buildreqs,
                         set(['pkgconfig(module)', 'pkgconfig(module2)']))

    def test_configure_ac_line_xdt_check_package(self):
        """
        Test configure_ac_line for the XFCE version of PKG_CHECK_MODULES
        """
        self.reqs.configure_ac_line(
            'XDT_CHECK_PACKAGE(prefix, '
            '[module = 2 module2 > 9], '
            'action-if-found, action-if-not-found)', False)
        self.assertEqual(self.reqs.buildreqs,
                         set(['pkgconfig(module)', 'pkgconfig(module2)']))

    def test_configure_ac_line_pkg_check_exists(self):
        """
        Test configure_ac_line for the PKG_CHECK_EXISTS macro
        """
        self.reqs.configure_ac_line('PKG_CHECK_EXISTS([module1 > 1 module2], '
                                   'action-if-found, '
                                   'action-if-not-found)', False)
        self.assertEqual(self.reqs.buildreqs,
                         set(['pkgconfig(module1)', 'pkgconfig(module2)']))

    def test_parse_configure_ac(self):
        """
        Test parse_configure_ac with changing () depths and package
        requirements
        """
        conf = config.Config("")
        content = 'AC_CHECK_FUNC([tgetent])\n'                   \
                  'XDT_CHECK_PACKAGE(prefix, '                   \
                  '[module = 2 module2 > 9], '                   \
                  'action-if-found, action-if-not-found)\n'      \
                  'next two lines should be read in batch ( \n'  \
                  'PROG_INTLTOOL\n'                              \
                  'AC_PROG_SED)\n'                               \
                  'GETTEXT_PACKAGE'
        with tempfile.TemporaryDirectory() as tmpd:
            with open(os.path.join(tmpd, 'fname'), 'w') as f:
                f.write(content)
            self.reqs.parse_configure_ac(os.path.join(tmpd, 'fname'), conf)

        self.assertEqual(self.reqs.buildreqs,
                         set(['gettext',
                              'ncurses-devel',
                              'perl(XML::Parser)',
                              'pkgconfig(module2)',
                              'pkgconfig(module)',
                              'intltool',
                              'sed']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_clean_python_req(self):
        """
        Test clean_python_req with a common python requirements string
        """
        self.assertEqual(buildreq.clean_python_req('requirement >= 1.1.2'),
                         'requirement')
        self.assertEqual(buildreq.clean_python_req('requirement ; python_version > 1.1.2'),
                         'requirement')
        self.assertEqual(buildreq.clean_python_req('requirement ; python_version < 1.1.2'),
                         '')
        self.assertEqual(buildreq.clean_python_req('requirement <= 1.1.2'),
                         'requirement')
        self.assertEqual(buildreq.clean_python_req('requirement = 1.1.2'),
                         'requirement')
        self.assertEqual(buildreq.clean_python_req('requirement \n ; rsa>= 1.1.2'),
                         'requirement')
        self.assertEqual(buildreq.clean_python_req('requirement != 1.1.2'),
                         'requirement')
        self.assertEqual(buildreq.clean_python_req('[:python > 2]'),
                         '')
        self.assertEqual(buildreq.clean_python_req('requirement ~= 1.1.2'),
                         'requirement')

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_clean_python_req_comment(self):
        """
        Test clean_python_req with a comment
        """
        self.assertEqual(buildreq.clean_python_req('# hello'), '')

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_clean_python_req_whitespace(self):
        """
        Test clean_python_req with strange whitespaced string
        """
        self.assertEqual(buildreq.clean_python_req('   requirement    < 1.1'),
                        'requirement')

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_grab_python_requirements(self):
        """
        Test grab_python_requirements with a reasonable requirements file
        """
        # buildreqs must include the requires also
        open_name = 'buildreq.util.open_auto'
        content = 'req1 <= 1.2.3\n' \
                  'req2 >= 1.55\n'  \
                  'req7 == 3.3.3\n'
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.grab_python_requirements('filename', [])

        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)', 'pypi(req7)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_grab_python_requirements_strange_file(self):
        """
        Test grab_python_requirements with a poorly written file
        """
        # buildreqs must include the requires also
        open_name = 'buildreq.util.open_auto'
        content = '    req1 <= 1.2.3\n   ' \
                  'req2    >= 1.55   \n'   \
                  '   req7 == 3.3.3\n    '
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.grab_python_requirements('filename', [])

        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)', 'pypi(req7)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires(self):
        """
        Test add_setup_py_requires with a single item in install_requires and
        setup_requires
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires=['req1']\n" \
                  "setup_requires=['req2']"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(req1)', 'pypi(req2)']))
        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires_multiline(self):
        """
        Test add_setup_py_requires with a multiline item in install_requires
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires=['req1',\n" \
                  "'req2',\n"                   \
                  "'req7']\n"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(req1)', 'pypi(req2)', 'pypi(req7)']))
        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)', 'pypi(req7)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires_multiline_formatted(self):
        """
        Test add_setup_py_requires with a multiline item in install_requires
        with brackets on their own lines.
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires=[\n "  \
                  "'req1',\n"              \
                  "'req2',\n"              \
                  "'req7',\n"              \
                  "]\n"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(req1)', 'pypi(req2)', 'pypi(req7)']))
        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)', 'pypi(req7)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires_multiline_variable(self):
        """
        Test add_setup_py_requires with multiline item in install_requires that
        contains a non-literal object.
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires=[\n" \
                  "reqvar,\n"            \
                  "'req1',\n"            \
                  "'req2'"               \
                  "]\n"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(req1)', 'pypi(req2)']))
        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires_multiline_install_requires_variable(self):
        """
        Test add_setup_py_requires with multiline item in install_requires that
        contains an extra bit of content that shouldn't be parsed as install_requires.
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires=[\n"   \
                  "'req1',\n"              \
                  "'req2'"                 \
                  "] + install_requires\n" \
                  "'bad']\n"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(req1)', 'pypi(req2)']))
        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires_variable(self):
        """
        Test add_setup_py_requires that contains a non-literal object.
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires=[reqname, 'req1', 'req2']\n"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(req1)', 'pypi(req2)']))
        self.assertEqual(self.reqs.requires["python3"], set(['pypi(req1)', 'pypi(req2)']))

    @patch('buildreq.pypidata.get_pypi_name', get_pypi_name_wrapper)
    def test_add_setup_py_requires_single_variable(self):
        """
        Test add_setup_py_requires with a single non-literal object
        """
        open_name = 'buildreq.util.open_auto'
        content = "install_requires='reqname'"
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open, create=True):
            self.reqs.add_setup_py_requires('filename', [])

        self.assertEqual(self.reqs.buildreqs, set(['pypi(reqname)']))
        self.assertEqual(self.reqs.requires['python3'], set(['pypi(reqname)']))

    def test_scan_for_configure_setup(self):
        """
        Test scan_for_configure with a mocked package structure. There is so
        much to test here that uses the same logic, a representative test
        should be sufficient.
        """
        conf = config.Config("")
        conf.config_opts['use_ninja'] = False
        with tempfile.TemporaryDirectory() as tmpd:
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'setup.py'), 'w').close()

            self.reqs.scan_for_configure(tmpd, "", conf)

        self.assertEqual(self.reqs.buildreqs,
                         set(['buildreq-distutils3']))

    def test_scan_for_configure_cmake(self):
        """
        Test scan_for_configure with a mocked package structure. There is so
        much to test here that uses the same logic, a representative test
        should be sufficient.
        """
        conf = config.Config("")
        conf.config_opts['use_ninja'] = False
        with tempfile.TemporaryDirectory() as tmpd:
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'CMakeLists.txt'), 'w').close()

            self.reqs.scan_for_configure(tmpd, "", conf)

        self.assertEqual(self.reqs.buildreqs,
                         set(['buildreq-cmake']))

    def test_scan_for_configure_scons(self):
        """
        Test scan_for_configure with a mocked package structure. There is so
        much to test here that uses the same logic, a representative test
        should be sufficient.
        """
        conf = config.Config("")
        conf.config_opts['use_ninja'] = False
        with tempfile.TemporaryDirectory() as tmpd:
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'SConstruct'), 'w').close()

            self.reqs.scan_for_configure(tmpd, "", conf)

        self.assertEqual(self.reqs.buildreqs,
                         set(['buildreq-scons']))

    def test_scan_for_configure_meson(self):
        """
        Test scan_for_configure with a mocked package structure. There is so
        much to test here that uses the same logic, a representative test
        should be sufficient.
        """
        conf = config.Config("")
        conf.config_opts['use_ninja'] = False
        with tempfile.TemporaryDirectory() as tmpd:
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'meson.build'), 'w').close()

            self.reqs.scan_for_configure(tmpd, "", conf)

        self.assertEqual(self.reqs.buildreqs,
                         set(['buildreq-meson']))

    def test_scan_for_configure_pypi(self):
        """
        Test scan_for_configure when distutils is being used for the build
        pattern to test pypi metadata handling.
        """
        orig_summary = buildreq.specdescription.default_summary
        orig_sscore = buildreq.specdescription.default_summary_score
        orig_pypi_name = buildreq.pypidata.get_pypi_name
        orig_pypi_meta = buildreq.pypidata.get_pypi_metadata
        name = "name"
        requires = ["abc",
                    "def"]
        pypi_requires = set(f"pypi({x})" for x in requires)
        summary = "summary"
        content = json.dumps({"name": name,
                              "summary": summary,
                              "requires": requires})
        buildreq.pypidata.get_pypi_name = MagicMock(return_value=True)
        buildreq.pypidata.get_pypi_metadata = MagicMock(return_value=content)
        with tempfile.TemporaryDirectory() as tmpd:
            conf = config.Config(tmpd)
            conf.config_opts['use_ninja'] = False
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'subdir', 'pyproject.toml'), 'w').close()
            self.reqs.scan_for_configure(os.path.join(tmpd, 'subdir'), "", conf)

        ssummary = buildreq.specdescription.default_summary
        buildreq.specdescription.default_summary = orig_summary
        buildreq.specdescription.default_summary_score = orig_sscore
        buildreq.pypidata.get_pypi_name = orig_pypi_name
        buildreq.pypidata.get_pypi_metadata = orig_pypi_meta

        self.assertEqual(self.reqs.pypi_provides, name)
        self.assertEqual(self.reqs.requires['python3'], pypi_requires)
        self.assertEqual(ssummary, summary)

    def test_scan_for_configure_pypi_override(self):
        """
        Test scan_for_configure when distutils is being used for the build
        pattern to test pypi metadata file override handling.
        """
        open_name = 'buildreq.open'
        orig_summary = buildreq.specdescription.default_summary
        orig_sscore = buildreq.specdescription.default_summary_score
        name = "name"
        summary = "summary"
        requires = ["req"]
        pypi_requires = set(f"pypi({x})" for x in requires)
        content = json.dumps({"name": name,
                              "summary": summary,
                              "requires": requires})
        m_open = mock_open(read_data=content)
        with tempfile.TemporaryDirectory() as tmpd:
            conf = config.Config(tmpd)
            conf.config_opts['use_ninja'] = False
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'subdir', 'pyproject.toml'), 'w').close()
            open(os.path.join(tmpd, 'pypi.json'), 'w').close()
            with patch(open_name, m_open, create=True):
                self.reqs.scan_for_configure(os.path.join(tmpd, 'subdir'), "", conf)

        ssummary = buildreq.specdescription.default_summary
        buildreq.specdescription.default_summary = orig_summary
        buildreq.specdescription.default_summary_score = orig_sscore

        self.assertEqual(self.reqs.pypi_provides, name)
        self.assertEqual(self.reqs.requires['python3'], pypi_requires)
        self.assertEqual(ssummary, summary)

    def test_scan_for_configure_setup_with_requires(self):
        """
        Test scan_for_configure when distutils is being used for the build
        pattern to test requires.txt handling.
        """
        self.reqs.add_pyproject_requires = MagicMock()
        self.reqs.add_setup_py_requires = MagicMock()
        self.reqs.grab_python_requirements = MagicMock()

        with tempfile.TemporaryDirectory() as tmpd:
            conf = config.Config(tmpd)
            conf.config_opts['use_ninja'] = False
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'subdir', 'setup.py'), 'w').close()
            open(os.path.join(tmpd, 'subdir', 'requires.txt'), 'w').close()
            self.reqs.scan_for_configure(os.path.join(tmpd, 'subdir'), "", conf)

        self.reqs.add_pyproject_requires.assert_not_called()
        self.reqs.add_setup_py_requires.assert_called_once()
        self.reqs.grab_python_requirements.assert_called_once()

    def test_scan_for_configure_ninja(self):
        """
        Test scan_for_configure when ninja is enabled.
        """
        conf = config.Config("")
        conf.config_opts['use_ninja'] = True
        with tempfile.TemporaryDirectory() as tmpd:
            os.mkdir(os.path.join(tmpd, 'subdir'))
            open(os.path.join(tmpd, 'setup.py'), 'w').close()

            self.reqs.scan_for_configure(tmpd, "", conf)

        self.assertEqual(self.reqs.buildreqs,
                         set(['buildreq-distutils3', 'ninja']))

    def test_parse_cmake_pkg_check_modules(self):
        """
        Test parse_cmake to ensure accurate detection of versioned and
        unversioned pkgconfig modules.
        """
        conf = config.Config("")
        conf.setup_patterns()
        content = 'pkg_check_modules(GLIB gio-unix-2.0>=2.46.0 glib-2.0 REQUIRED)'
        with tempfile.TemporaryDirectory() as tmpd:
            with open(os.path.join(tmpd, 'fname'), 'w') as f:
                f.write(content)
            self.reqs.parse_cmake(os.path.join(tmpd, 'fname'), conf.cmake_modules, False)

        self.assertEqual(self.reqs.buildreqs,
                         set(['pkgconfig(gio-unix-2.0)', 'pkgconfig(glib-2.0)']))

    def test_parse_cmake_pkg_check_modules_whitespace(self):
        """
        Test parse_cmake to ensure accurate handling of versioned
        pkgconfig modules with whitespace.
        """
        conf = config.Config("")
        conf.setup_patterns()
        content = 'pkg_check_modules(GLIB gio-unix-2.0 >= 2.46.0 glib-2.0 REQUIRED)'
        with tempfile.TemporaryDirectory() as tmpd:
            with open(os.path.join(tmpd, 'fname'), 'w') as f:
                f.write(content)
            self.reqs.parse_cmake(os.path.join(tmpd, 'fname'), conf.cmake_modules, False)

        self.assertEqual(self.reqs.buildreqs,
                         set(['pkgconfig(gio-unix-2.0)', 'pkgconfig(glib-2.0)']))

    def test_parse_cmake_pkg_check_modules_in_a_comment(self):
        """
        Test parse_cmake to ensure it ignores pkg_check_modules in comments.
        """
        conf = config.Config("")
        conf.setup_patterns()
        content = '''
# For example, consider the following patch to some CMakeLists.txt.
#     - pkg_check_modules(FOO REQUIRED foo>=1.0)
#     + pkg_check_modules(FOO REQUIRED foo>=2.0)
'''
        with tempfile.TemporaryDirectory() as tmpd:
            with open(os.path.join(tmpd, 'fname'), 'w') as f:
                f.write(content)
            self.reqs.parse_cmake(os.path.join(tmpd, 'fname'), conf.cmake_modules, False)

        self.assertEqual(self.reqs.buildreqs,
                         set([]))

    def test_parse_cmake_pkg_check_modules_variables(self):
        """
        Test parse_cmake to ensure accurate handling of versioned
        pkgconfig modules with variable version strings.
        """
        conf = config.Config("")
        conf.setup_patterns()
        content = 'pkg_check_modules(AVCODEC libavcodec${_avcodec_ver} libavutil$_avutil_ver)'
        with tempfile.TemporaryDirectory() as tmpd:
            with open(os.path.join(tmpd, 'fname'), 'w') as f:
                f.write(content)
            self.reqs.parse_cmake(os.path.join(tmpd, 'fname'), conf.cmake_modules, False)

        self.assertEqual(self.reqs.buildreqs,
                         set(['pkgconfig(libavcodec)', 'pkgconfig(libavutil)']))

    def test_parse_cmake_find_package(self):
        """
        Test parse_cmake to ensure accurate handling of find_package.
        """
        cmake_modules = {
            "valid": "valid",
            "valid_but_commented": "valid_but_commented",
            "different_name": "another_name",
            "qt6.module1": "qt6module1",
            "qt6.module2": "qt6module2",
            "kf6.module3": "kf6module3",
            "kf6.module4": "kf6module4",
            ".module5": "namodule5",
            ".module6": "namodule6"
        }
        content = '''
find_package(valid)
#find_package(foo)
  # find_package(valid_but_commented)
find_package(different_name)
find_package(Qt6 stuff
module1
module2)
find_package(KF6 stuff
module3
module4
)
find_package(NOT_HANDLED_NAMESPACE stuff
module5

module6
)
'''
        with tempfile.TemporaryDirectory() as tmpd:
            with open(os.path.join(tmpd, 'fname'), 'w') as f:
                f.write(content)
            self.reqs.parse_cmake(os.path.join(tmpd, 'fname'), cmake_modules, False)

        self.assertEqual(self.reqs.buildreqs,
                         set(['valid',
                              'another_name',
                              'qt6module1',
                              'qt6module2',
                              'kf6module3',
                              'kf6module4',
                              'namodule5',
                              'namodule6']))

    def test_r_desc_field_begin(self):
        """Test parsing of the first R description field."""
        lines = [
            "Field1: foo",
            "Field2: bar",
            "Field3: baz",
        ]
        result = buildreq._get_desc_field("Field1", "\n".join(lines))
        self.assertEqual(result, ["foo"])

    def test_r_desc_field_middle(self):
        """Test parsing of an R description field with surrounding fields."""
        lines = [
            "Field1: foo",
            "Field2: bar",
            "Field3: baz",
        ]
        result = buildreq._get_desc_field("Field2", "\n".join(lines))
        self.assertEqual(result, ["bar"])

    def test_r_desc_field_end(self):
        """Test parsing of the last R description field."""
        lines = [
            "Field1: foo",
            "Field2: bar",
            "Field3: baz",
        ]
        result = buildreq._get_desc_field("Field3", "\n".join(lines))
        self.assertEqual(result, ["baz"])

    def test_r_desc_field_middle_multiple_lines(self):
        """Test parsing of a multi-line R description field, with surrounding fields."""
        lines = [
            "Field1: foo",
            "Field2: bar1, bar2,",
            "  bar3, bar4",
            "Field3: baz",
        ]
        result = buildreq._get_desc_field("Field2", "\n".join(lines))
        self.assertEqual(result, ["bar1", "bar2", "bar3", "bar4"])

    def test_r_desc_field_end_multiple_lines(self):
        """Test parsing of the last R description field, consisting of multiple lines."""
        lines = [
            "Field1: foo",
            "Field2: bar",
            "Field3: baz1,",
            "        baz2",
        ]
        result = buildreq._get_desc_field("Field3", "\n".join(lines))
        self.assertEqual(result, ["baz1", "baz2"])

    def test_r_desc_field_middle_one_per_line(self):
        """Test parsing of an R description field with one entry per line."""
        lines = [
            "Field1: foo",
            "Field2:",
            "bar1,",
            "bar2",
            "Field3: baz",
        ]
        result = buildreq._get_desc_field("Field2", "\n".join(lines))
        self.assertEqual(result, ["bar1", "bar2"])

    def test_r_desc_field_trailing_whitespace(self):
        """Test parsing of an R description field with trailing whitespace."""
        lines = [
            "Field1: foo1, foo2    ",
        ]
        result = buildreq._get_desc_field("Field1", "\n".join(lines))
        self.assertEqual(result, ["foo1", "foo2"])

    def test_r_desc_field_trailing_comma(self):
        """Test parsing of an R description field with trailing comma."""
        lines = [
            "Field1: foo1, foo2,",
        ]
        result = buildreq._get_desc_field("Field1", "\n".join(lines))
        self.assertEqual(result, ["foo1", "foo2"])

    def test_r_desc_field_empty(self):
        """Test parsing of an R description field with an empty value."""
        lines = [
            "Field1:",
        ]
        result = buildreq._get_desc_field("Field1", "\n".join(lines))
        self.assertEqual(result, [])

    def test_r_desc_field_missing(self):
        """Test parsing of an R description field that is missing."""
        lines = [
            "Field1: foo, bar",
        ]
        result = buildreq._get_desc_field("Field2", "\n".join(lines))
        self.assertEqual(result, [])

    def test_parse_r_desc_depends(self):
        """Test parsing of R description Depends field."""
        pkgs = ['R-pkg1']
        open_name = 'buildreq.util.open_auto'
        content = 'Depends: pkg1'
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open):
            self.reqs.parse_r_description('filename', pkgs)
        self.assertTrue('R-pkg1' in self.reqs.buildreqs)

    def test_parse_r_desc_imports(self):
        """Test parsing of an R description Imports field."""
        pkgs = ['R-pkg2']
        open_name = 'buildreq.util.open_auto'
        content = 'Imports: pkg2'
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open):
            self.reqs.parse_r_description('filename', pkgs)
        self.assertTrue('R-pkg2' in self.reqs.buildreqs)

    def test_parse_r_desc_linkingto(self):
        """Test parsing of an R description LinkingTo field."""
        pkgs = ['R-pkg3']
        open_name = 'buildreq.util.open_auto'
        content = 'LinkingTo: pkg3'
        m_open = mock_open(read_data=content)
        with patch(open_name, m_open):
            self.reqs.parse_r_description('filename', pkgs)
        self.assertTrue('R-pkg3' in self.reqs.buildreqs)

    def test_parse_r_desc_multiple(self):
        """Test parsing of an R description file that captures multiple fields."""
        pkgs = [
            'R-pkg1',
            'R-pkg2',
            'R-pkg3',
            'R-pkg4',
        ]
        open_name = 'buildreq.util.open_auto'
        content = [
            'Field1: foo',
            'Imports: pkg1, pkg2,',
            '            pkg3   ',
            'LinkingTo: pkg4',
            'FieldFoo: bar',
        ]
        m_open = mock_open(read_data='\n'.join(content))
        with patch(open_name, m_open):
            self.reqs.parse_r_description('filename', pkgs)
        self.assertFalse('R-foo' in self.reqs.buildreqs)
        self.assertFalse('R-bar' in self.reqs.buildreqs)
        self.assertTrue('R-pkg1' in self.reqs.buildreqs)
        self.assertTrue('R-pkg2' in self.reqs.buildreqs)
        self.assertTrue('R-pkg3' in self.reqs.buildreqs)
        self.assertTrue('R-pkg4' in self.reqs.buildreqs)

    def test_parse_r_desc_not_in_os(self):
        """Test parsing of an R description file with some non-OS packages."""
        pkgs = [
            'R-pkg1',
        ]
        open_name = 'buildreq.util.open_auto'
        content = [
            'Imports: pkg1, pkg2',
            'Depends: pkg3',
        ]
        m_open = mock_open(read_data='\n'.join(content))
        with patch(open_name, m_open):
            self.reqs.parse_r_description('filename', pkgs)
        self.assertTrue('R-pkg1' in self.reqs.buildreqs)
        self.assertTrue('R-pkg1' in self.reqs.requires[None])
        # Names absent from the os-packages list are also added, because the
        # DESCRIPTION file is considered authoritative.
        for pkg in ['R-pkg2', 'R-pkg3']:
            self.assertTrue(pkg in self.reqs.buildreqs)
            self.assertTrue(pkg in self.reqs.requires[None])


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