godosa-engine

git clone https://git.godosa.eu/godosa-engine

master

raw · 5605 bytes

"""python3 -m unittest discover -s scripts -p 'test_*.py'"""
import contextlib
import io
import json
import tempfile
import unittest
from pathlib import Path

import godosa_licenses as gl


def nuspec(cache: Path, pid: str, ver: str, lic: str | None, copy: str = "© Someone"):
    d = cache / pid.lower() / ver
    d.mkdir(parents=True)
    licel = f'<license type="expression">{lic}</license>' if lic else '<license type="file">LICENSE</license>'
    (d / f"{pid.lower()}.nuspec").write_text(
        f'<?xml version="1.0"?><package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">'
        f"<metadata><id>{pid}</id><version>{ver}</version>{licel}<copyright>{copy}</copyright></metadata></package>")


def project(repo: Path, sln: str, rel: str, pkgs: dict[str, str]):
    """pkgs: "Id/ver" -> "runtime" | "build" (analyzer, nothing in the output)."""
    p = repo / rel
    f = repo / sln
    line = f'<Project Path="{rel}/{p.name}.csproj" />'
    old = f.read_text() if f.exists() else ""
    if line not in old:
        f.write_text(old + line + "\n")
    (p / "obj").mkdir(parents=True, exist_ok=True)
    (p / f"{p.name}.csproj").write_text("<Project/>")
    targets = {k: ({"type": "package", "runtime": {"lib/net10.0/X.dll": {}}} if kind == "runtime"
                   else {"type": "package", "runtime": {"lib/netstandard2.0/_._": {}}})
               for k, kind in pkgs.items()}
    (p / "obj" / "project.assets.json").write_text(json.dumps({
        "targets": {"net10.0": targets},
        "libraries": {k: {"type": "package"} for k in pkgs}}))


class LicensesTest(unittest.TestCase):
    def setUp(self):
        self.tmp = tempfile.TemporaryDirectory()
        self.repo = Path(self.tmp.name) / "repo"
        self.cache = Path(self.tmp.name) / "cache"
        self.repo.mkdir()
        self.cache.mkdir()
        self.cfg = gl.Config("Game.slnx", ("src/Game/",), "# Licences\n",
                             extra=[("runtime", "10", "MIT", "© R", "all")],
                             planned=[("Later", "2.0", "BSD|0", "managed", "nuspec")],
                             command="tool")
        (self.repo / "Game.slnx").write_text("")

    def tearDown(self):
        self.tmp.cleanup()

    def run_main(self, *argv) -> tuple[int, str]:
        err = io.StringIO()
        with contextlib.redirect_stderr(err):
            code = gl.main(list(argv), self.cfg, self.repo, self.cache)
        return code, err.getvalue()

    def test_write_then_check_is_clean_and_splits_shipped_from_test(self):
        nuspec(self.cache, "Pack", "1.0.0", "MIT", "© P")
        nuspec(self.cache, "xunit", "2.9.2", "Apache-2.0")
        project(self.repo, "Game.slnx", "src/Game", {"Pack/1.0.0": "runtime"})
        project(self.repo, "Game.slnx", "src/Game.Tests", {"xunit/2.9.2": "runtime", "Pack/1.0.0": "runtime"})
        self.assertEqual(self.run_main("--write")[0], 0)
        self.assertEqual(self.run_main(), (0, ""))
        md = (self.repo / "licenses.md").read_text()
        self.assertTrue(md.startswith("# Licences\n"))
        shipped, rest = md.split("## Not a package")
        self.assertIn("| Pack | 1.0.0 | MIT | © P | Game |  |", shipped)
        self.assertNotIn("xunit", shipped)
        self.assertIn("| runtime | 10 | MIT | © R | all |", rest)
        self.assertIn("| Later | 2.0 | BSD\\|0 | managed | nuspec |", rest)
        self.assertIn("| xunit | 2.9.2 | Apache-2.0 | Game.Tests |", rest)

    def test_stale_names_the_command(self):
        self.assertEqual(self.run_main(), (1, "licenses.md is stale: run tool --write\n"))

    def test_copyleft_ships_only_when_allowed(self):
        nuspec(self.cache, "OpenAL.Native", "1.0.0", "LGPL-2.0-or-later")
        project(self.repo, "Game.slnx", "src/Game.Tests", {"OpenAL.Native/1.0.0": "runtime"})
        self.assertEqual(self.run_main("--write")[0], 0)   # test project: not shipped
        project(self.repo, "Game.slnx", "src/Game", {"OpenAL.Native/1.0.0": "runtime"})
        self.assertEqual(self.run_main("--write"), (1, "NuGet OpenAL.Native 1.0.0: copyleft licence LGPL-2.0-or-later ships\n"))
        self.cfg.allow_shipped["OpenAL.Native"] = "shared library"
        self.assertEqual(self.run_main("--write")[0], 0)
        self.assertIn("| shared library |", (self.repo / "licenses.md").read_text())

    def test_build_only_package_is_not_shipped(self):
        nuspec(self.cache, "Analyzer", "1.0.0", "GPL-3.0-only")
        project(self.repo, "Game.slnx", "src/Game", {"Analyzer/1.0.0": "build"})
        self.assertEqual(self.run_main("--write")[0], 0)

    def test_unknown_licence_fails_until_overridden(self):
        nuspec(self.cache, "FileLicensed", "1.0.0", None)
        project(self.repo, "Game.slnx", "src/Game.Tests", {"FileLicensed/1.0.0": "runtime"})
        self.assertEqual(self.run_main("--write")[0], 1)
        self.cfg.overrides["FileLicensed"] = "MIT"
        self.assertEqual(self.run_main("--write")[0], 0)

    def test_projects_come_from_the_configured_solution(self):
        nuspec(self.cache, "GplLib", "1.0.0", "GPL-3.0-only")
        project(self.repo, "Other.slnx", "src/Game", {"GplLib/1.0.0": "runtime"})
        self.assertEqual(self.run_main("--write")[0], 0)
        self.cfg.solution = "Other.slnx"
        self.assertEqual(self.run_main("--write")[0], 1)

    def test_missing_assets_fails(self):
        (self.repo / "src/Game").mkdir(parents=True)
        (self.repo / "Game.slnx").write_text('<Project Path="src/Game/Game.csproj" />')
        self.assertEqual(self.run_main("--write")[0], 1)


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