Files
crossdesk/apps/ios/tests/third_party_licenses_test.py
T

189 lines
9.0 KiB
Python

"""Regression coverage for notice validation and build-specific source links."""
import copy
import importlib.util
import json
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import patch
def load_script(name):
path = Path(__file__).resolve().parents[1] / "scripts" / f"{name}.py"
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
licenses = load_script("generate_third_party_licenses")
metadata = load_script("prepare_source_metadata")
class LicenseValidationTest(unittest.TestCase):
def setUp(self):
temporary = tempfile.TemporaryDirectory(prefix="crossdesk-ios-licenses-")
self.addCleanup(temporary.cleanup)
self.root = Path(temporary.name)
self.mini = self.root / "deps/submodules/minirtc"
self.mini.mkdir(parents=True)
self.notice = self.mini / "LICENSE"
self.notice.write_text("Reviewed license text\n")
self.recipe = self.mini / "thirdparty/opus/xmake.lua"
self.recipe.parent.mkdir(parents=True)
self.recipe.write_text('package("opus")\n')
self.source = self.mini / "transport.cpp"
self.source.write_text("int transport() { return 1; }\n")
self.git("init", "-q")
self.git("add", ".")
self.git("commit", "-qm", "Reviewed source")
self.reviewed_revision = self.git("rev-parse", "HEAD")
self.bundle = self.root / "ThirdPartyLicenses.json"
self.bundle.write_text('{"schemaVersion": 1, "components": []}\n')
self.archive = self.root / "packages/o/opus/1.0/abcd/lib/libopus.a"
self.archive.parent.mkdir(parents=True)
self.archive.write_bytes(b"reviewed archive")
self.inputs = self.root / "inputs.sha256"
self.inputs.write_text(f"{licenses.digest(self.archive.read_bytes())} {self.archive}\n")
self.target = self.root / "target-info.txt"
self.target.write_text(str(self.root / "packages/a/asio/1.0/abcd/include") + "\n")
self.lock = {
"bundle_sha256": licenses.digest(self.bundle.read_bytes()),
"toolchain": {"xmake_min_version": "3.1.1"},
"components": [
{
"id": "minirtc", "version": self.reviewed_revision,
"source": {"kind": "repository", "url": metadata.MINIRTC_URL,
"revision": self.reviewed_revision},
"documents": [{"path": str(self.notice.relative_to(self.root)),
"sha256": licenses.digest(self.notice.read_bytes())}],
},
{
"id": "opus", "package": "opus", "version": "1.0",
"source": {"kind": "archive", "url": "https://example.com/opus.tar.gz"},
"recipe_path": str(self.recipe.relative_to(self.root)),
"recipe_sha256": licenses.digest(self.recipe.read_bytes()),
},
{"id": "asio", "package": "asio", "version": "1.0",
"source": {"kind": "archive", "url": "https://example.com/asio.tar.gz"}},
],
}
for name, value in (("REPO", self.root), ("OUTPUT", self.bundle)):
patcher = patch.object(licenses, name, value)
patcher.start()
self.addCleanup(patcher.stop)
def git(self, *args):
return subprocess.check_output(
["git", "-C", str(self.mini), "-c", "user.name=Test",
"-c", "user.email=test@example.com", "-c", "commit.gpgsign=false", *args],
text=True).strip()
def verify(self, preflight=False, **kwargs):
licenses.verify(self.lock, None if preflight else self.inputs,
None if preflight else self.target, **kwargs)
def test_source_only_commit_keeps_reviewed_catalog_and_uses_actual_links(self):
self.source.write_text("int transport() { return 2; }\n")
self.git("add", ".")
self.git("commit", "-qm", "Change transport implementation")
current = self.git("rev-parse", "HEAD")
self.assertNotEqual(current, self.reviewed_revision)
original_lock = copy.deepcopy(self.lock)
original_bundle = self.bundle.read_bytes()
self.verify(preflight=True)
self.verify()
sources = metadata.component_sources(self.lock, current)
self.assertEqual(sources["minirtc"]["version"], current)
self.assertEqual(sources["minirtc"]["sourceURL"], f"{metadata.MINIRTC_URL}/tree/{current}")
self.assertEqual(sources["opus"]["buildSourceURL"],
f"{metadata.MINIRTC_URL}/tree/{current}/thirdparty/opus")
self.assertEqual(self.lock, original_lock)
self.assertEqual(self.bundle.read_bytes(), original_bundle)
def test_changed_license_is_rejected_before_compilation(self):
self.notice.write_text("Changed license text\n")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*LICENSE"):
self.verify(preflight=True)
def test_changed_recipe_is_rejected_before_compilation(self):
self.recipe.write_text('package("different-opus")\n')
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*xmake.lua"):
self.verify(preflight=True)
def test_changed_bundle_is_rejected_before_compilation(self):
self.bundle.write_text("{}\n")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch:"):
self.verify(preflight=True)
def test_unreviewed_header_only_dependency_is_rejected(self):
self.target.write_text(str(self.root / "packages/a/asio/2.0/abcd/include") + "\n")
with self.assertRaisesRegex(ValueError, "Unreviewed iOS dependency: asio 2.0"):
self.verify()
def test_missing_dependency_is_rejected(self):
self.target.write_text("")
with self.assertRaisesRegex(ValueError, "License/build dependency mismatch; missing:.*asio"):
self.verify()
def test_simulator_cache_keeps_dependency_and_archive_validation(self):
(self.root / "packages").rename(self.root / "packages-simulator")
self.archive = Path(str(self.archive).replace("/packages/", "/packages-simulator/"))
self.inputs.write_text(self.inputs.read_text().replace("/packages/", "/packages-simulator/"))
self.target.write_text(self.target.read_text().replace("/packages/", "/packages-simulator/"))
self.verify()
valid_target = self.target.read_text()
self.target.write_text(valid_target.replace("/asio/1.0/", "/asio/2.0/"))
with self.assertRaisesRegex(ValueError, "Unreviewed iOS dependency: asio 2.0"):
self.verify()
self.target.write_text(valid_target)
self.archive.write_bytes(b"changed simulator archive")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*libopus.a"):
self.verify()
def test_changed_native_archive_is_rejected(self):
self.archive.write_bytes(b"changed archive")
with self.assertRaisesRegex(ValueError, "Source checksum mismatch: .*libopus.a"):
self.verify()
def test_minimum_and_newer_xmake_versions_are_accepted(self):
version = self.root / "xmake-version.txt"
for output in ("xmake v3.1.1", "xmake v3.1.2\n", "xmake v3.1.10, A build utility\n",
"xmake v3.2.0\n", "xmake v3.10.0\n", "xmake v4.0.0\n",
"\x1b[1mxmake v3.1.1+HEAD.3ba37a0d4, A build utility\x1b[0m\n"):
with self.subTest(output=output):
version.write_text(output)
self.verify(xmake_version=version)
def test_older_or_unrecognized_xmake_versions_are_rejected(self):
version = self.root / "xmake-version.txt"
for output in ("xmake v3.1.0\n", "xmake v3.0.99\n", "xmake v2.99.99\n",
"xmake version unknown\n", "xmake v3.1\n", "xmake v3.1.1invalid\n", ""):
with self.subTest(output=output):
version.write_text(output)
with self.assertRaisesRegex(ValueError, "Xmake 3.1.1 or newer is required"):
self.verify(xmake_version=version)
class ComponentSourceTest(unittest.TestCase):
def test_all_minirtc_components_and_custom_recipes_follow_build_revision(self):
lock_path = Path(__file__).resolve().parents[1] / "licenses/sources.json"
lock = json.loads(lock_path.read_text())
reviewed = copy.deepcopy(lock)
revision = "a" * 40
base = f"{metadata.MINIRTC_URL}/tree/{revision}"
sources = metadata.component_sources(lock, revision)
for name in ("minirtc", "inih", "webrtc"):
self.assertEqual(sources[name], {"version": revision, "sourceURL": base})
self.assertEqual(sources["openssl3"], {"buildSourceURL": base + "/thirdparty/openssl"})
self.assertNotIn("asio", sources)
self.assertNotIn("crossdesk", sources)
self.assertEqual(lock, reviewed)
if __name__ == "__main__":
unittest.main()