#!/usr/bin/env python3

# Unit tests for decopy
# Copyright: 2018, Maximiliano Curia <maxy@debian.org>
#
# License: ISC
#  Permission to use, copy, modify, and/or distribute this software for any
#  purpose with or without fee is hereby granted, provided that the above
#  copyright notice and this permission notice appear in all copies.
#  .
#  THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
#  REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
#  AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
#  INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
#  LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
#  OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
#  PERFORMANCE OF THIS SOFTWARE.

import json
import unittest
from pathlib import Path

from decopy.licensing import licenses


class TestSpdx(unittest.TestCase):
    def _check_license_match(self, license_, matcher, expected, ignore=None):
        for key in ("licenseText", "standardLicenseHeader", "licenseExceptionText"):
            if key not in license_ or not license_[key]:
                continue
            if ignore and key in ignore:
                continue
            text = license_[key]
            result = matcher(text)
            self.assertEqual(result, expected, msg=f"with key={key}")

    def _open_license_and_check(self, filename, matcher, expected, ignore=None):
        with (Path("tests/testdata/licenses") / filename).open() as fp:
            license_ = json.load(fp)
        return self._check_license_match(license_, matcher, expected, ignore)

    def _open_exception_and_check(self, filename, matcher, expected, ignore=None):
        with (Path("tests/testdata/exceptions") / filename).open() as fp:
            license_ = json.load(fp)
        return self._check_license_match(license_, matcher, expected, ignore)

    @staticmethod
    def _find_licenses_matcher(text):
        found = licenses.find_licenses(licenses.clean_comments(text))
        return " or ".join(sorted(found))

    def test_spdx_a(self):
        self._open_license_and_check("AAL.json", self._find_licenses_matcher, "AAL")
        self._open_license_and_check(
            "Abstyles.json",
            self._find_licenses_matcher,
            "Abstyles",
        )
        self._open_license_and_check(
            "Adobe-2006.json",
            self._find_licenses_matcher,
            "Adobe-2006",
        )
        self._open_license_and_check(
            "Adobe-Glyph.json",
            self._find_licenses_matcher,
            "Adobe-Glyph",
        )
        self._open_license_and_check("ADSL.json", self._find_licenses_matcher, "ADSL")
        self._open_license_and_check(
            "AFL-1.1.json",
            self._find_licenses_matcher,
            "Academic-1.1",
        )
        self._open_license_and_check(
            "AFL-1.2.json",
            self._find_licenses_matcher,
            "Academic-1.2",
        )
        self._open_license_and_check(
            "AFL-2.0.json",
            self._find_licenses_matcher,
            "Academic-2",
        )
        self._open_license_and_check(
            "AFL-2.1.json",
            self._find_licenses_matcher,
            "Academic-2.1",
        )
        self._open_license_and_check(
            "AFL-3.0.json",
            self._find_licenses_matcher,
            "Academic-3",
        )
        self._open_license_and_check(
            "Afmparse.json",
            self._find_licenses_matcher,
            "Afmparse",
        )
        self._open_license_and_check(
            "Aladdin.json",
            self._find_licenses_matcher,
            "Aladdin",
        )
        self._open_license_and_check(
            "AMDPLPA.json",
            self._find_licenses_matcher,
            "AMDPLPA",
        )
        self._open_license_and_check("AML.json", self._find_licenses_matcher, "AML")
        self._open_license_and_check(
            "AMPAS.json",
            self._find_licenses_matcher,
            "BSD-2-clause-AMPAS",
        )
        self._open_license_and_check(
            "ANTLR-PD.json",
            self._find_licenses_matcher,
            "ANTLR-PD",
        )
        self._open_license_and_check(
            "Apache-1.0.json",
            self._find_licenses_matcher,
            "Apache-1",
        )
        self._open_license_and_check(
            "Apache-1.1.json",
            self._find_licenses_matcher,
            "Apache-1.1",
        )
        self._open_license_and_check(
            "Apache-2.0.json",
            self._find_licenses_matcher,
            "Apache-2",
        )
        self._open_license_and_check(
            "APAFML.json",
            self._find_licenses_matcher,
            "APAFML",
        )
        self._open_license_and_check(
            "APL-1.0.json",
            self._find_licenses_matcher,
            "APL-1",
            ignore={"standardLicenseHeader"},
        )
        self._open_license_and_check(
            "APSL-1.0.json",
            self._find_licenses_matcher,
            "APSL-1",
        )
        self._open_license_and_check(
            "APSL-1.1.json",
            self._find_licenses_matcher,
            "APSL-1.1",
        )
        self._open_license_and_check(
            "APSL-1.2.json",
            self._find_licenses_matcher,
            "APSL-1.2",
        )
        self._open_license_and_check(
            "APSL-2.0.json",
            self._find_licenses_matcher,
            "APSL-2",
        )
        self._open_license_and_check(
            "Artistic-1.0-cl8.json",
            self._find_licenses_matcher,
            "Artistic-1-cl8",
        )
        self._open_license_and_check(
            "Artistic-1.0-Perl.json",
            self._find_licenses_matcher,
            "Artistic-1-Perl",
        )
        self._open_license_and_check(
            "Artistic-1.0.json",
            self._find_licenses_matcher,
            "Artistic-1",
        )
        self._open_license_and_check(
            "Artistic-2.0.json",
            self._find_licenses_matcher,
            "Artistic-2",
        )

    def test_spdx_b(self):
        self._open_license_and_check(
            "Bahyph.json",
            self._find_licenses_matcher,
            "Bahyph",
        )
        self._open_license_and_check("Barr.json", self._find_licenses_matcher, "Barr")
        self._open_license_and_check(
            "BitTorrent-1.0.json",
            self._find_licenses_matcher,
            "BitTorrent-1",
        )
        self._open_license_and_check(
            "BitTorrent-1.1.json",
            self._find_licenses_matcher,
            "BitTorrent-1.1",
            # licenseText embeds the 1.0 preamble verbatim, which fools the
            # version-capture regex into returning "BitTorrent-1".
            # standardLicenseHeader correctly returns "BitTorrent-1.1".
            ignore={"licenseText"},
        )
        self._open_license_and_check(
            "Borceux.json",
            self._find_licenses_matcher,
            "Borceux",
        )
        self._open_license_and_check(
            "BSL-1.0.json",
            self._find_licenses_matcher,
            "BSL-1",
        )
        self._open_license_and_check(
            "bzip2-1.0.5.json",
            self._find_licenses_matcher,
            "BSD-like-bzip2-1.0.5",
        )
        self._open_license_and_check(
            "bzip2-1.0.6.json",
            self._find_licenses_matcher,
            "BSD-like-bzip2-1.0.6",
        )

    def test_spdx_c(self):
        self._open_license_and_check(
            "Caldera.json",
            self._find_licenses_matcher,
            "Caldera",
        )
        self._open_license_and_check(
            "CATOSL-1.1.json",
            self._find_licenses_matcher,
            "CATOSL-1.1",
        )
        self._open_license_and_check(
            "CDDL-1.0.json",
            self._find_licenses_matcher,
            "CDDL-1",
        )
        self._open_license_and_check(
            "CDDL-1.1.json",
            self._find_licenses_matcher,
            "CDDL-1.1",
        )
        self._open_license_and_check(
            "CECILL-1.0.json",
            self._find_licenses_matcher,
            "CeCILL-1",
        )
        self._open_license_and_check(
            "CECILL-1.1.json",
            self._find_licenses_matcher,
            "CeCILL-1.1",
        )
        self._open_license_and_check(
            "CECILL-2.0.json",
            self._find_licenses_matcher,
            "CeCILL-2",
        )
        self._open_license_and_check(
            "CECILL-2.1.json",
            self._find_licenses_matcher,
            "CeCILL-2.1",
        )
        self._open_license_and_check(
            "CECILL-B.json",
            self._find_licenses_matcher,
            "CeCILL-B",
        )
        self._open_license_and_check(
            "CECILL-C.json",
            self._find_licenses_matcher,
            "CeCILL-C",
        )

        self._open_license_and_check(
            "ClArtistic.json",
            self._find_licenses_matcher,
            "ClArtistic",
        )

        self._open_license_and_check(
            "CNRI-Jython.json",
            self._find_licenses_matcher,
            "CNRI-Jython",
        )
        self._open_license_and_check(
            "CNRI-Python-GPL-Compatible.json",
            self._find_licenses_matcher,
            "CNRI-Python-GPL-Compatible",
        )
        self._open_license_and_check(
            "CNRI-Python.json",
            self._find_licenses_matcher,
            "CNRI-Python",
        )

        self._open_license_and_check(
            "Condor-1.1.json",
            self._find_licenses_matcher,
            "Condor-1.1",
        )

        self._open_license_and_check(
            "CPAL-1.0.json",
            self._find_licenses_matcher,
            "CPAL-1",
        )
        self._open_license_and_check(
            "CPL-1.0.json",
            self._find_licenses_matcher,
            "CPL-1",
        )
        self._open_license_and_check(
            "CPOL-1.02.json",
            self._find_licenses_matcher,
            "CPOL-1.02",
        )
        self._open_license_and_check(
            "Crossword.json",
            self._find_licenses_matcher,
            "Crossword",
        )
        self._open_license_and_check(
            "CrystalStacker.json",
            self._find_licenses_matcher,
            "CrystalStacker",
        )
        self._open_license_and_check(
            "CUA-OPL-1.0.json",
            self._find_licenses_matcher,
            "CUA-OPL-1",
        )
        self._open_license_and_check("Cube.json", self._find_licenses_matcher, "Cube")
        self._open_license_and_check("curl.json", self._find_licenses_matcher, "curl")

    def test_spdx_d(self):
        self._open_license_and_check(
            "D-FSL-1.0.json",
            self._find_licenses_matcher,
            "D-FSL",
        )
        self._open_license_and_check(
            "diffmark.json",
            self._find_licenses_matcher,
            "diffmark",
        )
        self._open_license_and_check("DOC.json", self._find_licenses_matcher, "DOC")
        self._open_license_and_check(
            "Dotseqn.json",
            self._find_licenses_matcher,
            "Dotseqn",
        )
        self._open_license_and_check("DSDP.json", self._find_licenses_matcher, "DSDP")
        self._open_license_and_check(
            "dvipdfm.json",
            self._find_licenses_matcher,
            "dvipdfm",
        )

    def test_spdx_e(self):
        self._open_license_and_check(
            "ECL-1.0.json",
            self._find_licenses_matcher,
            "ECL-1",
        )
        self._open_license_and_check(
            "ECL-2.0.json",
            self._find_licenses_matcher,
            "ECL-2",
        )
        self._open_license_and_check(
            "eCos-2.0.json",
            self._find_licenses_matcher,
            "GPL-2+ with eCos-2 exception",
        )
        self._open_license_and_check(
            "EFL-1.0.json",
            self._find_licenses_matcher,
            "EFL-1",
        )
        self._open_license_and_check(
            "EFL-2.0.json",
            self._find_licenses_matcher,
            "EFL-2",
        )
        self._open_license_and_check(
            "eGenix.json",
            self._find_licenses_matcher,
            "eGenix",
        )
        self._open_license_and_check(
            "Entessa.json",
            self._find_licenses_matcher,
            "BSD-like-Entessa",
        )
        self._open_license_and_check(
            "EPL-1.0.json",
            self._find_licenses_matcher,
            "EPL-1",
        )
        self._open_license_and_check(
            "ErlPL-1.1.json",
            self._find_licenses_matcher,
            "ErlPL-1.1",
        )
        self._open_license_and_check(
            "EUDatagrid.json",
            self._find_licenses_matcher,
            "EUDatagrid",
        )
        self._open_license_and_check(
            "EUPL-1.0.json",
            self._find_licenses_matcher,
            "EUPL-1",
        )
        self._open_license_and_check(
            "EUPL-1.1.json",
            self._find_licenses_matcher,
            "EUPL-1.1",
        )
        self._open_license_and_check(
            "Eurosym.json",
            self._find_licenses_matcher,
            "Eurosym",
        )

    def test_spdx_f(self):
        self._open_license_and_check("Fair.json", self._find_licenses_matcher, "Fair")
        self._open_license_and_check(
            "Frameworx-1.0.json",
            self._find_licenses_matcher,
            "Frameworx-1",
        )
        self._open_license_and_check(
            "FreeImage.json",
            self._find_licenses_matcher,
            "FreeImage-1",
        )
        self._open_license_and_check("FSFAP.json", self._find_licenses_matcher, "FSFAP")
        self._open_license_and_check("FSFUL.json", self._find_licenses_matcher, "FSFUL")
        self._open_license_and_check(
            "FSFULLR.json",
            self._find_licenses_matcher,
            "FSFULLR",
        )
        self._open_license_and_check("FTL.json", self._find_licenses_matcher, "FTL")

    def test_spdx_g(self):
        self._open_license_and_check(
            "GFDL-1.1.json",
            self._find_licenses_matcher,
            "GFDL-1.1+",
            ignore={"licenseText"},
        )
        self._open_license_and_check(
            "GFDL-1.2.json",
            self._find_licenses_matcher,
            "GFDL-NIV-1.2+",
        )
        self._open_license_and_check(
            "GFDL-1.3.json",
            self._find_licenses_matcher,
            "GFDL-NIV-1.3+",
        )
        self._open_license_and_check(
            "Giftware.json",
            self._find_licenses_matcher,
            "Giftware",
        )
        self._open_license_and_check("GL2PS.json", self._find_licenses_matcher, "GL2PS")
        self._open_license_and_check("Glide.json", self._find_licenses_matcher, "Glide")
        self._open_license_and_check(
            "Glulxe.json",
            self._find_licenses_matcher,
            "Glulxe",
        )
        self._open_license_and_check(
            "gnuplot.json",
            self._find_licenses_matcher,
            "gnuplot",
        )
        self._open_license_and_check(
            "gSOAP-1.3b.json",
            self._find_licenses_matcher,
            "gSOAP-1.3",
        )

    def test_spdx_h(self):
        self._open_license_and_check(
            "HaskellReport.json",
            self._find_licenses_matcher,
            "HaskellReport",
        )
        self._open_license_and_check(
            "HPND.json", self._find_licenses_matcher, "MIT-like-HPND"
        )

    def test_spdx_i(self):
        self._open_license_and_check(
            "IBM-pibs.json",
            self._find_licenses_matcher,
            "IBM-pibs",
        )
        self._open_license_and_check(
            "ICU.json",
            self._find_licenses_matcher,
            "MIT-like-icu",
        )
        self._open_license_and_check("IJG.json", self._find_licenses_matcher, "IJG")
        self._open_license_and_check(
            "ImageMagick.json",
            self._find_licenses_matcher,
            "ImageMagick",
        )
        self._open_license_and_check(
            "iMatix.json",
            self._find_licenses_matcher,
            "iMatix",
        )
        self._open_license_and_check(
            "Imlib2.json",
            self._find_licenses_matcher,
            "MIT-Imlib2",
        )
        self._open_license_and_check(
            "Info-ZIP.json",
            self._find_licenses_matcher,
            "Info-ZIP",
        )
        self._open_license_and_check(
            "Intel-ACPI.json",
            self._find_licenses_matcher,
            "Intel-ACPI",
        )
        self._open_license_and_check("Intel.json", self._find_licenses_matcher, "Intel")
        self._open_license_and_check(
            "Interbase-1.0.json",
            self._find_licenses_matcher,
            "Interbase-1",
        )
        self._open_license_and_check("IPA.json", self._find_licenses_matcher, "IPA-1")
        self._open_license_and_check(
            "IPL-1.0.json",
            self._find_licenses_matcher,
            "IPL-1",
        )
        self._open_license_and_check("ISC.json", self._find_licenses_matcher, "ISC")

    def test_spdx_j(self):
        self._open_license_and_check(
            "JasPer-2.0.json",
            self._find_licenses_matcher,
            "JasPer-2",
        )
        self._open_license_and_check("JSON.json", self._find_licenses_matcher, "JSON")

    def test_spdx_l(self):
        self._open_license_and_check(
            "LAL-1.2.json",
            self._find_licenses_matcher,
            "LAL-1.2",
        )
        self._open_license_and_check(
            "LAL-1.3.json",
            self._find_licenses_matcher,
            "LAL-1.3",
        )
        self._open_license_and_check(
            "Latex2e.json",
            self._find_licenses_matcher,
            "Latex2e",
        )
        self._open_license_and_check(
            "Leptonica.json",
            self._find_licenses_matcher,
            "Leptonica",
        )
        self._open_license_and_check(
            "Libpng.json",
            self._find_licenses_matcher,
            "Libpng",
        )
        self._open_license_and_check(
            "libtiff.json",
            self._find_licenses_matcher,
            "libtiff",
        )
        self._open_license_and_check(
            "LiLiQ-P-1.1.json",
            self._find_licenses_matcher,
            "LiLiQ-P-1.1",
        )
        self._open_license_and_check(
            "LiLiQ-R-1.1.json",
            self._find_licenses_matcher,
            "LiLiQ-R-1.1",
        )
        self._open_license_and_check(
            "LiLiQ-Rplus-1.1.json",
            self._find_licenses_matcher,
            "LiLiQ-R+-1.1",
        )
        self._open_license_and_check(
            "LPL-1.0.json",
            self._find_licenses_matcher,
            "LPL-1",
        )
        self._open_license_and_check(
            "LPL-1.02.json",
            self._find_licenses_matcher,
            "LPL-1.02",
        )

    def test_spdx_m(self):
        self._open_license_and_check(
            "MakeIndex.json",
            self._find_licenses_matcher,
            "MakeIndex",
        )
        self._open_license_and_check("MirOS.json", self._find_licenses_matcher, "MirOS")
        self._open_license_and_check(
            "Motosoto.json",
            self._find_licenses_matcher,
            "Motosoto",
            ignore={"standardLicenseHeader"},
        )
        self._open_license_and_check(
            "mpich2.json",
            self._find_licenses_matcher,
            "mpich",
        )
        self._open_license_and_check(
            "MPL-1.0.json",
            self._find_licenses_matcher,
            "MPL-1",
        )
        self._open_license_and_check(
            "MPL-1.1.json",
            self._find_licenses_matcher,
            "MPL-1.1",
        )
        self._open_license_and_check(
            "MPL-2.0-no-copyleft-exception.json",
            self._find_licenses_matcher,
            "MPL-2 with no-copyleft exception",
            ignore={"licenseText"},
        )
        self._open_license_and_check(
            "MPL-2.0.json",
            self._find_licenses_matcher,
            "MPL-2",
        )
        self._open_license_and_check("MS-PL.json", self._find_licenses_matcher, "MS-PL")
        self._open_license_and_check("MS-RL.json", self._find_licenses_matcher, "MS-RL")
        self._open_license_and_check("MTLL.json", self._find_licenses_matcher, "MTLL")
        self._open_license_and_check(
            "Multics.json",
            self._find_licenses_matcher,
            "Multics",
        )
        self._open_license_and_check(
            "Mup.json",
            self._find_licenses_matcher,
            "BSD-like-Mup",
        )

    def test_spdx_n(self):
        self._open_license_and_check(
            "NASA-1.3.json",
            self._find_licenses_matcher,
            "NASA-1.3",
        )
        self._open_license_and_check(
            "Naumen.json",
            self._find_licenses_matcher,
            "Naumen",
        )
        self._open_license_and_check(
            "NBPL-1.0.json",
            self._find_licenses_matcher,
            "NBPL-1",
        )
        self._open_license_and_check("NCSA.json", self._find_licenses_matcher, "NCSA")
        self._open_license_and_check(
            "NetCDF.json",
            self._find_licenses_matcher,
            "NetCDF",
        )
        self._open_license_and_check(
            "Newsletr.json",
            self._find_licenses_matcher,
            "Newsletr",
        )
        self._open_license_and_check("NGPL.json", self._find_licenses_matcher, "NGPL")
        self._open_license_and_check(
            "NLOD-1.0.json",
            self._find_licenses_matcher,
            "NLOD-1",
        )
        self._open_license_and_check("NLPL.json", self._find_licenses_matcher, "NLPL")
        self._open_license_and_check(
            "Nokia.json",
            self._find_licenses_matcher,
            "Nokia",
        )
        self._open_license_and_check(
            "Net-SNMP.json",
            self._find_licenses_matcher,
            "Net-SNMP",
        )
        self._open_license_and_check("NOSL.json", self._find_licenses_matcher, "NOSL-1")
        self._open_license_and_check("Noweb.json", self._find_licenses_matcher, "Noweb")
        self._open_license_and_check(
            "NPL-1.0.json",
            self._find_licenses_matcher,
            "NPL-1",
        )
        # The header included in the file is NPL-1
        self._open_license_and_check(
            "NPL-1.1.json",
            self._find_licenses_matcher,
            "MPL-1.1_NPL-1.1",
            ignore={"standardLicenseHeader"},
        )
        self._open_license_and_check(
            "NPOSL-3.0.json",
            self._find_licenses_matcher,
            "NPOSL-3",
        )
        self._open_license_and_check("NRL.json", self._find_licenses_matcher, "NRL")
        self._open_license_and_check("NTP.json", self._find_licenses_matcher, "NTP")
        self._open_license_and_check(
            "Nunit.json",
            self._find_licenses_matcher,
            "Zlib-acknowledgement",
        )

    def test_spdx_o(self):
        self._open_license_and_check(
            "OCCT-PL.json",
            self._find_licenses_matcher,
            "OCCT-PL",
        )
        self._open_license_and_check(
            "OCLC-2.0.json",
            self._find_licenses_matcher,
            "OCLC-2",
        )
        self._open_license_and_check(
            "ODbL-1.0.json",
            self._find_licenses_matcher,
            "ODbL-1",
        )
        self._open_license_and_check(
            "OFL-1.0.json",
            self._find_licenses_matcher,
            "OFL-1",
        )
        self._open_license_and_check(
            "OFL-1.1.json",
            self._find_licenses_matcher,
            "OFL-1.1",
        )
        self._open_license_and_check("OGTSL.json", self._find_licenses_matcher, "OGTSL")
        self._open_license_and_check(
            "OLDAP-1.1.json",
            self._find_licenses_matcher,
            "OLDAP-1.1",
        )
        self._open_license_and_check(
            "OLDAP-1.2.json",
            self._find_licenses_matcher,
            "OLDAP-1.2",
        )
        self._open_license_and_check(
            "OLDAP-1.3.json",
            self._find_licenses_matcher,
            "OLDAP-1.3",
        )
        self._open_license_and_check(
            "OLDAP-1.4.json",
            self._find_licenses_matcher,
            "OLDAP-1.4",
        )
        self._open_license_and_check(
            "OLDAP-2.0.1.json",
            self._find_licenses_matcher,
            "OLDAP-2.0.1",
        )
        self._open_license_and_check(
            "OLDAP-2.0.json",
            self._find_licenses_matcher,
            "OLDAP-2",
        )
        self._open_license_and_check(
            "OLDAP-2.1.json",
            self._find_licenses_matcher,
            "OLDAP-2.1",
        )
        self._open_license_and_check(
            "OLDAP-2.2.1.json",
            self._find_licenses_matcher,
            "OLDAP-2.2.1",
        )
        self._open_license_and_check(
            "OLDAP-2.2.2.json",
            self._find_licenses_matcher,
            "OLDAP-2.2.2",
        )
        self._open_license_and_check(
            "OLDAP-2.2.json",
            self._find_licenses_matcher,
            "OLDAP-2.2",
        )
        self._open_license_and_check(
            "OLDAP-2.3.json",
            self._find_licenses_matcher,
            "OLDAP-2.3",
        )
        self._open_license_and_check(
            "OLDAP-2.4.json",
            self._find_licenses_matcher,
            "OLDAP-2.4",
        )
        self._open_license_and_check(
            "OLDAP-2.5.json",
            self._find_licenses_matcher,
            "OLDAP-2.5",
        )
        self._open_license_and_check(
            "OLDAP-2.6.json",
            self._find_licenses_matcher,
            "OLDAP-2.6",
        )
        self._open_license_and_check(
            "OLDAP-2.7.json",
            self._find_licenses_matcher,
            "OLDAP-2.7",
        )
        self._open_license_and_check(
            "OLDAP-2.8.json",
            self._find_licenses_matcher,
            "OLDAP-2.8",
        )
        self._open_license_and_check("OML.json", self._find_licenses_matcher, "OML")
        # The dual OpenSSL+SSLeay licence reports the single OpenSSL id: the
        # embedded SSLeay (BSD family) is suppressed when the OpenSSL Project
        # marker is present.
        self._open_license_and_check(
            "OpenSSL.json",
            self._find_licenses_matcher,
            "OpenSSL",
        )
        self._open_license_and_check(
            "OPL-1.0.json",
            self._find_licenses_matcher,
            "OPL-1",
        )
        self._open_license_and_check(
            "OSET-PL-2.1.json",
            self._find_licenses_matcher,
            "OSET-PL-2.1",
        )
        self._open_license_and_check(
            "OSL-1.0.json",
            self._find_licenses_matcher,
            "OSL-1",
        )
        self._open_license_and_check(
            "OSL-1.1.json",
            self._find_licenses_matcher,
            "OSL-1.1",
        )
        self._open_license_and_check(
            "OSL-2.0.json",
            self._find_licenses_matcher,
            "OSL-2",
        )
        self._open_license_and_check(
            "OSL-2.1.json",
            self._find_licenses_matcher,
            "OSL-2.1",
        )
        self._open_license_and_check(
            "OSL-3.0.json",
            self._find_licenses_matcher,
            "OSL-3",
        )

    def test_spdx_p(self):
        self._open_license_and_check(
            "PDDL-1.0.json",
            self._find_licenses_matcher,
            "PDDL-1",
        )
        self._open_license_and_check(
            "PHP-3.0.json",
            self._find_licenses_matcher,
            "PHP-3",
        )
        self._open_license_and_check(
            "PHP-3.01.json",
            self._find_licenses_matcher,
            "PHP-3.01",
        )
        self._open_license_and_check(
            "Plexus.json",
            self._find_licenses_matcher,
            "Plexus",
        )
        self._open_license_and_check(
            "PostgreSQL.json",
            self._find_licenses_matcher,
            "PostgreSQL",
        )
        self._open_license_and_check(
            "psfrag.json",
            self._find_licenses_matcher,
            "psfrag",
        )
        self._open_license_and_check(
            "psutils.json",
            self._find_licenses_matcher,
            "psutils",
        )
        self._open_license_and_check(
            "PSF-2.0.json",
            self._find_licenses_matcher,
            "PSF-2",
        )
        # The 2.0 / 2.0.1 collections suppress their embedded CNRI and CWI/MIT
        # parts so each is reported under its single Python id.
        self._open_license_and_check(
            "Python-2.0.json",
            self._find_licenses_matcher,
            "Python-2",
        )
        self._open_license_and_check(
            "Python-2.0.1.json",
            self._find_licenses_matcher,
            "Python-2.0.1",
        )

    def test_spdx_q(self):
        self._open_license_and_check("Qhull.json", self._find_licenses_matcher, "Qhull")
        self._open_license_and_check(
            "QPL-1.0.json",
            self._find_licenses_matcher,
            "QPL-1",
        )

    def test_spdx_r(self):
        self._open_license_and_check("Rdisc.json", self._find_licenses_matcher, "Rdisc")
        self._open_license_and_check(
            "RHeCos-1.1.json",
            self._find_licenses_matcher,
            "RHeCos-1.1",
        )
        self._open_license_and_check(
            "RPL-1.1.json",
            self._find_licenses_matcher,
            "RPL-1.1",
        )
        self._open_license_and_check(
            "RPL-1.5.json",
            self._find_licenses_matcher,
            "RPL-1.5",
        )
        self._open_license_and_check(
            "RPSL-1.0.json",
            self._find_licenses_matcher,
            "RPSL-1",
        )
        self._open_license_and_check(
            "RSA-MD.json",
            self._find_licenses_matcher,
            "RSA-MD",
        )
        self._open_license_and_check(
            "RSCPL.json",
            self._find_licenses_matcher,
            "RSCPL-1",
        )
        self._open_license_and_check("Ruby.json", self._find_licenses_matcher, "Ruby")

    def test_spdx_s(self):
        self._open_license_and_check(
            "SAX-PD.json",
            self._find_licenses_matcher,
            "SAX-PD",
        )
        self._open_license_and_check(
            "Saxpath.json",
            self._find_licenses_matcher,
            "BSD-like-Saxpath",
        )
        self._open_license_and_check("SCEA.json", self._find_licenses_matcher, "SCEA-1")
        self._open_license_and_check(
            "Sendmail.json",
            self._find_licenses_matcher,
            "Sendmail",
        )
        self._open_license_and_check(
            "SGI-B-1.0.json",
            self._find_licenses_matcher,
            "SGI-B-1",
        )
        self._open_license_and_check(
            "SGI-B-1.1.json",
            self._find_licenses_matcher,
            "SGI-B-1.1",
        )
        self._open_license_and_check(
            "SGI-B-2.0.json",
            self._find_licenses_matcher,
            "SGI-B-2",
        )
        self._open_license_and_check(
            "SimPL-2.0.json",
            self._find_licenses_matcher,
            "SimPL-2",
        )
        self._open_license_and_check(
            "SISSL-1.2.json",
            self._find_licenses_matcher,
            "SISSL-1.2",
        )
        self._open_license_and_check(
            "SISSL.json",
            self._find_licenses_matcher,
            "SISSL-1.1",
        )
        self._open_license_and_check(
            "Sleepycat.json",
            self._find_licenses_matcher,
            "Sleepycat",
        )
        self._open_license_and_check(
            "SMLNJ.json",
            self._find_licenses_matcher,
            "MIT-like-SMLNJ",
        )
        self._open_license_and_check("SMPPL.json", self._find_licenses_matcher, "SMPPL")
        self._open_license_and_check("SNIA.json", self._find_licenses_matcher, "SNIA-1")
        self._open_license_and_check(
            "Spencer-86.json",
            self._find_licenses_matcher,
            "Spencer-86",
        )
        self._open_license_and_check(
            "Spencer-94.json",
            self._find_licenses_matcher,
            "Spencer-94",
        )
        self._open_license_and_check(
            "Spencer-99.json",
            self._find_licenses_matcher,
            "Spencer-99",
        )
        self._open_license_and_check(
            "SPL-1.0.json",
            self._find_licenses_matcher,
            "SPL-1",
        )
        self._open_license_and_check(
            "StandardML-NJ.json",
            self._find_licenses_matcher,
            "MIT-like-SMLNJ",
        )
        self._open_license_and_check(
            "SugarCRM-1.1.3.json",
            self._find_licenses_matcher,
            "SugarCRM-1.1.3",
        )
        self._open_license_and_check("SWL.json", self._find_licenses_matcher, "SWL")

    def test_spdx_t(self):
        self._open_license_and_check("TCL.json", self._find_licenses_matcher, "TCL")
        self._open_license_and_check(
            "TMate.json",
            self._find_licenses_matcher,
            "BSD-like-TMate",
        )
        self._open_license_and_check(
            "TORQUE-1.1.json",
            self._find_licenses_matcher,
            "TORQUE-1.1",
        )
        self._open_license_and_check(
            "TOSL.json",
            self._find_licenses_matcher,
            "BSD-like-TOSL",
        )

    def test_spdx_u(self):
        self._open_license_and_check(
            "Unicode-TOU.json",
            self._find_licenses_matcher,
            "Unicode-TOU",
        )
        self._open_license_and_check(
            "Unlicense.json",
            self._find_licenses_matcher,
            "Unlicense",
        )
        self._open_license_and_check(
            "UPL-1.0.json",
            self._find_licenses_matcher,
            "UPL-1",
        )

    def test_spdx_v(self):
        self._open_license_and_check("Vim.json", self._find_licenses_matcher, "Vim")
        self._open_license_and_check(
            "VOSTROM.json",
            self._find_licenses_matcher,
            "VOSTROM",
        )
        self._open_license_and_check(
            "VSL-1.0.json",
            self._find_licenses_matcher,
            "VSL-1",
        )

    def test_spdx_w(self):
        self._open_license_and_check(
            "W3C.json",
            self._find_licenses_matcher,
            "W3C",
            ignore={"standardLicenseHeader"},
        )
        self._open_license_and_check(
            "Watcom-1.0.json",
            self._find_licenses_matcher,
            "Watcom-1",
        )
        self._open_license_and_check(
            "Wsuipa.json",
            self._find_licenses_matcher,
            "Wsuipa",
        )
        self._open_license_and_check("WTFPL.json", self._find_licenses_matcher, "WTFPL")
        # wxWindows.json (deprecated SPDX ID) now contains only the exception
        # notice, not the combined LGPL+exception text of the old data.
        self._open_license_and_check(
            "wxWindows.json",
            self._find_licenses_matcher,
            "",
        )

    def test_spdx_x(self):
        self._open_license_and_check("X11.json", self._find_licenses_matcher, "X11")
        self._open_license_and_check("Xerox.json", self._find_licenses_matcher, "Xerox")
        self._open_license_and_check(
            "XFree86-1.1.json",
            self._find_licenses_matcher,
            "XFree86-1.1",
        )
        self._open_license_and_check(
            "xinetd.json",
            self._find_licenses_matcher,
            "xinetd",
        )
        self._open_license_and_check("Xnet.json", self._find_licenses_matcher, "Expat")
        self._open_license_and_check("xpp.json", self._find_licenses_matcher, "xpp")
        self._open_license_and_check("XSkat.json", self._find_licenses_matcher, "XSkat")

    def test_spdx_y(self):
        self._open_license_and_check(
            "YPL-1.0.json",
            self._find_licenses_matcher,
            "YPL-1",
        )
        self._open_license_and_check(
            "YPL-1.1.json",
            self._find_licenses_matcher,
            "YPL-1.1",
        )

    def test_spdx_z(self):
        self._open_license_and_check("Zed.json", self._find_licenses_matcher, "Zed")
        self._open_license_and_check(
            "Zend-2.0.json",
            self._find_licenses_matcher,
            "Zend-2",
        )
        self._open_license_and_check(
            "Zimbra-1.3.json",
            self._find_licenses_matcher,
            "Zimbra-1.3",
        )
        self._open_license_and_check(
            "Zimbra-1.4.json",
            self._find_licenses_matcher,
            "Zimbra-1.4",
        )
        self._open_license_and_check(
            "zlib-acknowledgement.json",
            self._find_licenses_matcher,
            "Zlib-acknowledgement",
        )
        self._open_license_and_check("Zlib.json", self._find_licenses_matcher, "Zlib")
        self._open_license_and_check(
            "ZPL-1.1.json",
            self._find_licenses_matcher,
            "ZPL-1.1",
        )
        self._open_license_and_check(
            "ZPL-2.0.json",
            self._find_licenses_matcher,
            "ZPL-2",
        )
        self._open_license_and_check(
            "ZPL-2.1.json",
            self._find_licenses_matcher,
            "ZPL-2.1",
        )


class TestSpdxExceptions(unittest.TestCase):
    @staticmethod
    def _load_license(spdx_id):
        d = json.loads(
            (Path("tests/testdata/licenses") / f"{spdx_id}.json").read_text()
        )
        return d.get("standardLicenseHeader") or d["licenseText"]

    @staticmethod
    def _load_exception(filename):
        d = json.loads((Path("tests/testdata/exceptions") / filename).read_text())
        return d["licenseExceptionText"]

    @staticmethod
    def _gpl_matcher(text):
        from decopy.licensing import license_parsers, licenses

        return license_parsers.parse_gpl(licenses.clean_comments(text), None, "GPL")

    @staticmethod
    def _agpl_matcher(text):
        from decopy.licensing import license_parsers, licenses

        return license_parsers.parse_gpl(licenses.clean_comments(text), None, "AGPL")

    @staticmethod
    def _lgpl_matcher(text):
        from decopy.licensing import license_parsers, licenses

        return license_parsers.parse_gpl(licenses.clean_comments(text), None, "LGPL")

    @staticmethod
    def _apache_matcher(text):
        from decopy.licensing import license_parsers, licenses

        return license_parsers.parse_apache(
            licenses.clean_comments(text), None, "Apache"
        )

    @staticmethod
    def _mit_matcher(text):
        from decopy.licensing import license_parsers, license_subs, licenses

        return license_parsers.parse_exceptions(
            licenses.clean_comments(text), "MIT", license_subs.MIT_EXCEPTION_SUBS
        )

    @staticmethod
    def _cc_by_sa_matcher(text):
        from decopy.licensing import license_parsers, license_subs, licenses

        return license_parsers.parse_exceptions(
            licenses.clean_comments(text),
            "CC-BY-SA",
            license_subs.CC_BY_SA_EXCEPTION_SUBS,
        )

    @staticmethod
    def _qpl_matcher(text):
        from decopy.licensing import license_parsers, licenses

        return license_parsers.parse_qpl(licenses.clean_comments(text), None, "QPL")

    @staticmethod
    def _bsd_matcher(text):
        from decopy.licensing import license_parsers, license_subs, licenses

        return license_parsers.parse_exceptions(
            licenses.clean_comments(text), "BSD", license_subs.BSD_EXCEPTION_SUBS
        )

    def _check(self, filename, expected, matcher=None):
        if matcher is None:
            matcher = self._gpl_matcher
        with (Path("tests/testdata/exceptions") / filename).open() as fp:
            d = json.load(fp)
        result = matcher(d["licenseExceptionText"])
        self.assertEqual(result, expected)

    def test_autoconf_passes(self):
        self._check("389-exception.json", "GPL-2 with 389 exception")
        self._check("Autoconf-exception-2.0.json", "GPL with AutoConf exception")
        self._check("Autoconf-exception-generic.json", "GPL with AutoConf exception")
        self._check("Classpath-exception-2.0.json", "GPL with ClassPath exception")
        self._check(
            "Classpath-exception-2.0-short.json", "GPL with ClassPath exception"
        )
        self._check("Fawkes-Runtime-exception.json", "GPL with ClassPath_MIF exception")

    def test_gcc_passes(self):
        self._check("GCC-exception-2.0.json", "GPL with GCC exception")
        self._check("CLISP-exception-2.0.json", "GPL-2 with CLISP exception")
        self._check("DigiRule-FOSS-exception.json", "GPL with DigiRule-FOSS exception")
        self._check("eCos-exception-2.0.json", "GPL with eCos-2 exception")
        self._check("FLTK-exception.json", "GPL with FLTK exception")
        self._check("Font-exception-2.0.json", "GPL with Font exception")
        self._check("freertos-exception-2.0.json", "GPL with FreeRTOS exception")
        self._check("gnu-javamail-exception.json", "GPL with GNU-JavaMail exception")
        self._check("i2p-gpl-java-exception.json", "GPL with i2p-Java exception")
        self._check("Libtool-exception.json", "GPL with LibTool exception")
        self._check("LZMA-exception.json", "GPL with LZMA exception")
        self._check("mif-exception.json", "GPL with MIF exception")
        self._check("Bison-exception-2.2.json", "GPL with Bison exception")
        self._check("u-boot-exception-2.0.json", "GPL with u-boot exception")

    # --- xfail entries (matcher changes needed in 5a) ---

    def test_autoconf_3(self):
        self._check("Autoconf-exception-3.0.json", "GPL-3 with AutoConf-3 exception")

    def test_autoconf_generic_3(self):
        self._check(
            "Autoconf-exception-generic-3.0.json", "GPL-3 with AutoConf-3 exception"
        )

    def test_autoconf_macro(self):
        self._check(
            "Autoconf-exception-macro.json", "GPL with AutoConf-macro exception"
        )

    def test_bison_1_24(self):
        self._check("Bison-exception-1.24.json", "GPL with Bison-1.24 exception")

    def test_classpath_independent_modules(self):
        self._check(
            "Independent-modules-exception.json",
            "GPL with Independent-modules exception",
        )

    def test_gcc_note(self):
        self._check("GCC-exception-2.0-note.json", "GPL with GCC exception")

    def test_gcc_3_1(self):
        self._check("GCC-exception-3.1.json", "GPL-3 with GCC-3.1 exception")

    def test_gnat(self):
        self._check("GNAT-exception.json", "GPL with GNAT exception")

    def test_gnu_compiler(self):
        self._check("GNU-compiler-exception.json", "GPL with GNU-compiler exception")

    def test_gpl_cc(self):
        self._check("GPL-CC-1.0.json", "GPL-2 with GPL-CC exception")

    def test_gpl_cc_combined(self):
        """Combined license header + GPL-CC: version comes from the base licence."""
        gplcc = self._load_exception("GPL-CC-1.0.json")

        for lic_id, group, expected in [
            ("LGPL-2.1", "LGPL", "LGPL-2.1 with GPL-CC exception"),
            ("LGPL-2.0", "LGPL", "LGPL-2 with GPL-CC exception"),
            ("GPL-2.0", "GPL", "GPL-2 with GPL-CC exception"),
        ]:
            with self.subTest(lic_id=lic_id):
                lic = self._load_license(lic_id)
                combined = lic + "\n\n" + gplcc
                from decopy.licensing import license_parsers, licenses

                result = license_parsers.parse_gpl(
                    licenses.clean_comments(combined), None, group
                )
                self.assertEqual(result, expected)

    def test_gpl3_linking(self):
        self._check(
            "GPL-3.0-linking-exception.json", "GPL-3 with GPL-3-linking exception"
        )

    def test_gpl3_linking_source(self):
        self._check(
            "GPL-3.0-linking-source-exception.json",
            "GPL-3 with GPL-3-linking-source exception",
        )

    def test_gpl3_interface(self):
        self._check(
            "GPL-3.0-interface-exception.json", "GPL-3 with GPL-3-interface exception"
        )

    def test_gpl3_389_ds(self):
        self._check(
            "GPL-3.0-389-ds-base-exception.json", "GPL with GPL-3-389-ds exception"
        )

    def test_gstreamer_2005(self):
        self._check("GStreamer-exception-2005.json", "GPL with GStreamer exception")

    def test_gstreamer_2008(self):
        self._check("GStreamer-exception-2008.json", "GPL with GStreamer exception")

    def test_gmsh(self):
        self._check("Gmsh-exception.json", "GPL with Gmsh exception")

    def test_linux_syscall(self):
        self._check("Linux-syscall-note.json", "GPL with Linux-syscall exception")

    def test_erlang_otp(self):
        self._check(
            "erlang-otp-linking-exception.json", "GPL with erlang-otp exception"
        )

    def test_harbour(self):
        self._check("harbour-exception.json", "GPL with harbour exception")

    def test_romic(self):
        self._check(
            "romic-exception.json", "AGPL-3 with romic exception", self._agpl_matcher
        )

    def test_rsync_linking(self):
        self._check("rsync-linking-exception.json", "GPL with rsync-linking exception")

    def test_stunnel(self):
        self._check("stunnel-exception.json", "GPL with stunnel exception")

    def test_swi(self):
        self._check("SWI-exception.json", "GPL with SWI exception")

    def test_texinfo(self):
        self._check("Texinfo-exception.json", "GPL-3 with Texinfo exception")

    def test_ubdl(self):
        self._check("UBDL-exception.json", "GPL-2+ with UBDL exception")

    def test_lgpl_qt_passes(self):
        self._check(
            "Nokia-Qt-exception-1.1.json",
            "LGPL-2.1 with Qt1.1 exception",
            self._lgpl_matcher,
        )
        self._check(
            "OCCT-exception-1.0.json",
            "LGPL-2.1 with OCCT-1 exception",
            self._lgpl_matcher,
        )
        self._check(
            "WxWindows-exception-3.1.json",
            "LGPL with WxWindows-3.1+ exception",
            self._lgpl_matcher,
        )

    def test_cgal_linking(self):
        self._check(
            "CGAL-linking-exception.json",
            "LGPL with CGAL-linking exception",
            self._lgpl_matcher,
        )

    def test_digia_qt(self):
        self._check(
            "Digia-Qt-LGPL-exception-1.1.json",
            "LGPL-2.1 with Digia-Qt exception",
            self._lgpl_matcher,
        )

    def test_ocaml_lgpl(self):
        self._check(
            "OCaml-LGPL-linking-exception.json",
            "LGPL with OCaml-LGPL exception",
            self._lgpl_matcher,
        )

    def test_qt_gpl(self):
        self._check(
            "Qt-GPL-exception-1.0.json",
            "LGPL with Qt-GPL-1 exception",
            self._lgpl_matcher,
        )

    def test_qt_lgpl_1_1(self):
        self._check(
            "Qt-LGPL-exception-1.1.json",
            "LGPL-2.1 with Qt-LGPL-1.1 exception",
            self._lgpl_matcher,
        )

    def test_qwt(self):
        self._check(
            "Qwt-exception-1.0.json", "LGPL with Qwt-1 exception", self._lgpl_matcher
        )

    def test_simple_library_usage(self):
        self._check(
            "Simple-Library-Usage-exception.json",
            "LGPL-1+ with Simple-Library-Usage exception",
            self._lgpl_matcher,
        )

    def test_libpri_openh323(self):
        self._check(
            "libpri-OpenH323-exception.json",
            "LGPL with libpri-OpenH323 exception",
            self._lgpl_matcher,
        )

    def test_polyparse(self):
        self._check(
            "polyparse-exception.json",
            "LGPL with polyparse exception",
            self._lgpl_matcher,
        )

    # --- OpenSSL/security family ---

    def test_openssl_passes(self):
        self._check(
            "openvpn-openssl-exception.json", "GPL with openvpn-openssl exception"
        )

    def test_bootloader(self):
        self._check("Bootloader-exception.json", "GPL with Bootloader exception")

    def test_openssl_cryptsetup(self):
        self._check(
            "cryptsetup-OpenSSL-exception.json", "GPL with OpenSSL-cryptsetup exception"
        )

    def test_openssl_kvirc(self):
        self._check("kvirc-openssl-exception.json", "GPL with OpenSSL-kvirc exception")

    def test_rrdtool_floss(self):
        self._check(
            "RRDtool-FLOSS-exception-2.0.json", "GPL with RRDtool-FLOSS exception"
        )

    def test_openssl_sqlitestudio(self):
        self._check(
            "sqlitestudio-OpenSSL-exception.json",
            "GPL with OpenSSL-sqlitestudio exception",
        )

    def test_openssl_vsftpd(self):
        self._check(
            "vsftpd-openssl-exception.json", "GPL with OpenSSL-vsftpd exception"
        )

    def test_openssl_x11vnc(self):
        self._check(
            "x11vnc-openssl-exception.json", "GPL with OpenSSL-x11vnc exception"
        )

    # --- Apache/MIT/other base licences ---

    def test_fmt(self):
        self._check("fmt-exception.json", "MIT with fmt exception", self._mit_matcher)

    def test_gnome_examples(self):
        self._check(
            "GNOME-examples-exception.json",
            "CC-BY-SA with GNOME-examples exception",
            self._cc_by_sa_matcher,
        )

    def test_openjdk_assembly(self):
        self._check(
            "OpenJDK-assembly-exception-1.0.json", "GPL with OpenJDK-assembly exception"
        )

    def test_ps_or_pdf_font(self):
        self._check(
            "PS-or-PDF-font-exception-20170817.json",
            "AGPL-3 with PS-or-PDF-font exception",
            self._agpl_matcher,
        )

    def test_qpl_inria(self):
        self._check(
            "QPL-1.0-INRIA-2004-exception.json",
            "QPL with QPL-INRIA-2004 exception",
            self._qpl_matcher,
        )

    def test_sane(self):
        self._check("SANE-exception.json", "GPL with SANE exception")

    def test_shl_2(self):
        self._check("SHL-2.0.json", "Apache with SHL-2 exception", self._apache_matcher)

    def test_shl_2_1(self):
        self._check(
            "SHL-2.1.json", "Apache with SHL-2.1 exception", self._apache_matcher
        )

    def test_swift(self):
        self._check(
            "Swift-exception.json", "Apache with Swift exception", self._apache_matcher
        )

    def test_universal_foss(self):
        self._check(
            "Universal-FOSS-exception-1.0.json", "GPL with Universal-FOSS exception"
        )

    # --- remaining / obscure ---

    def test_asterisk(self):
        self._check("Asterisk-exception.json", "GPL with Asterisk exception")

    def test_asterisk_linking(self):
        self._check(
            "Asterisk-linking-protocols-exception.json",
            "GPL with Asterisk-linking exception",
        )

    # --- coverage gap sweep ---

    def test_mxml(self):
        self._check(
            "mxml-exception.json", "Apache with mxml exception", self._apache_matcher
        )

    def test_llvm(self):
        self._check(
            "LLVM-exception.json", "Apache with LLVM exception", self._apache_matcher
        )

    def test_pcre2(self):
        self._check(
            "PCRE2-exception.json", "BSD with PCRE2 exception", self._bsd_matcher
        )

    def test_lgpl3_linking(self):
        self._check(
            "LGPL-3.0-linking-exception.json",
            "LGPL-3 with LGPL-3-linking exception",
            self._lgpl_matcher,
        )

    def test_llgpl(self):
        self._check("LLGPL.json", "LGPL-2.1 with LLGPL exception", self._lgpl_matcher)

    def test_kicad_libraries(self):
        self._check(
            "KiCad-libraries-exception.json",
            "CC-BY-SA with KiCad-libraries exception",
            self._cc_by_sa_matcher,
        )


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