blob: b6beeb53a49ffa026ec2f162a079eeba25dd91e7 [file] [log] [blame]
# Copyright 2014 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test the license_lib module."""
import json
import os
from unittest import mock
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.licensing import licenses_lib
# pylint: disable=protected-access
class LicenseLibTest(cros_test_lib.TempDirTestCase):
"""Limited tests for license_lib."""
LICENSE_PUBLIC = "Gentoo Package Stock"
LICENSE_CUSTOM = "Custom"
def setUp(self):
"""Sets up the filesystem for the tests."""
# Licenses for testing:
# key - Name of the license.
# contents - The contents of the license file itself.
# dir - Location of the file containing the license text on the FS.
# board - The board argument to use when testing the license.
# type - The license type (gentoo stock or custom).
# [skip_test] - Skip the tests for that license.
self.licenses = {
"TPL": {
"contents": "Third Party License",
"dir": "src/third_party/portage-stable/licenses/TPL",
"board": "foo",
"type": self.LICENSE_PUBLIC,
},
"PUB": {
"contents": "Public License",
"dir": "src/third_party/chromiumos-overlay/licenses/PUB",
"board": "foo",
"type": self.LICENSE_CUSTOM,
},
"CCL": {
"contents": "ChromeOS Custom License",
"dir": "src/private-overlays/chromeos-overlay/licenses/CCL",
"board": "chromeos",
"type": self.LICENSE_CUSTOM,
},
"CPL": {
"contents": "ChromeOS Partner License",
"dir": (
"src/private-overlays/chromeos-partner-overlay/licenses/CPL"
),
"board": "chromeos-partner",
"type": self.LICENSE_CUSTOM,
},
"FTL": {
"contents": "Fake Test License",
"dir": "src/overlays/overlay-foo/licenses/FTL",
"board": "foo",
"type": self.LICENSE_CUSTOM,
},
"OOR": {
"contents": "Out Of Reach",
"dir": "src/overlays/overlay-bar/licenses/OOR",
"skip_test": True,
},
licenses_lib.TAINTED: {
"contents": licenses_lib.TAINTED,
"dir": "src/overlays/overlay-foo/licenses/TAINTED",
"board": "foo",
"type": licenses_lib.TAINTED,
},
}
# Overlays for testing.
# dir: Overlay base directory.
# repo-name: The repo-name setting for the overlay.
# masters: The masters list for the overlay.
overlays = [
{
"dir": "src/overlays/overlay-foo",
"repo-name": "foo",
"masters": ["chromiumos", "portage-stable"],
},
{
"dir": "src/overlays/overlay-bar",
"repo-name": "bar",
"masters": [],
},
{
"dir": "src/private-overlays/overlay-foo-private",
"repo-name": "foo-private",
"masters": ["foo", "chromeos-partner"],
},
{
"dir": "src/private-overlays/chromeos-overlay",
"repo-name": "chromeos",
"masters": ["chromiumos", "portage-stable"],
},
{
"dir": "src/private-overlays/chromeos-partner-overlay",
"repo-name": "chromeos-partner",
"masters": ["chromeos"],
},
{
"dir": "src/third_party/chromiumos-overlay",
"repo-name": "chromiumos",
"masters": ["portage-stable"],
},
{
"dir": "src/third_party/portage-stable",
"repo-name": "portage-stable",
"masters": [],
},
]
# Template for overlay/metadata/layout.conf.
layout_template = """
repo-name = %(repo_name)s
masters = %(masters)s
"""
# Convenience variables for ebuild dictionary building.
foo_eb_dir = os.path.join(
self.tempdir, "src/overlays/overlay-foo/category"
)
foo_ec_dir = os.path.join(
self.tempdir, "src/overlays/overlay-foo/eclass"
)
_priv_rel_path = "src/private-overlays/overlay-foo-private/category"
foopriv_eb_dir = os.path.join(self.tempdir, _priv_rel_path)
# Ebuilds for testing.
# key: Package name of the ebuild.
# dir: .ebuild file location.
# board: Overlay that contains the ebuild.
# license: The license the ebuild uses.
self.ebuilds = {
"ftl-pkg": {
"dir": os.path.join(foo_eb_dir, "ftl-pkg/ftl-pkg-1.ebuild"),
"board": "foo",
"license": "FTL",
},
"ttl-pkg": {
"dir": os.path.join(foo_eb_dir, "ttl-pkg/ttl-pkg-1.ebuild"),
"board": "foo",
"license": licenses_lib.TAINTED,
},
"pub-pkg": {
"dir": os.path.join(foo_eb_dir, "pub-pkg/pub-pkg-1.ebuild"),
"board": "foo",
"license": "PUB",
},
"pub-cls": {
"dir": os.path.join(foo_ec_dir, "pub-cls.eclass"),
"board": "foo",
"license": "PUB",
},
"tpl-pkg": {
"dir": os.path.join(foo_eb_dir, "tpl-pkg/tpl-pkg-1.ebuild"),
"board": "foo",
"license": "TPL",
},
"ccl-pkg": {
"dir": os.path.join(foopriv_eb_dir, "ccl-pkg/ccl-pkg-1.ebuild"),
"board": "foo-private",
"license": "CCL",
},
}
self.build_infos = {
"metapackage-with-files": {
"files": {
"CATEGORY": "virtual",
"CONTENTS": """dir /foo
sym /sym -> / 1611355469
obj /file bd1b4ffa168f50b0d45571dae51eefc7 1611355468""",
"LICENSE": "metapackage",
"PF": "metapackage-with-files-1",
},
"expected_exception": licenses_lib.PackageCorrectnessError,
},
"metapackage-valid": {
"files": {
"CATEGORY": "virtual",
"CONTENTS": "",
"LICENSE": "metapackage",
"PF": "metapackage-valid-1",
},
"expected_exception": None,
},
}
# .ebuild content template.
ebuild_template = 'LICENSE="%(license)s"'
D = cros_test_lib.Directory
# Much of the file layout has to be an exact match due to hard coding of
# a few repos. Prepare for lisp mode.
file_layout = (
D(
"src",
(
D(
"overlays",
(
D(
"overlay-foo",
(
D(
"category",
(
D("ftl-pkg", ("ftl-pkg-1.ebuild",)),
D("pub-pkg", ("pub-pkg-1.ebuild",)),
D("tpl-pkg", ("tpl-pkg-1.ebuild",)),
D("ttl-pkg", ("ttl-pkg-1.ebuild",)),
),
),
D("eclass", ("pub-cls.eclass",)),
D("licenses", ("FTL",)),
D("metadata", ("layout.conf",)),
),
),
D(
"overlay-bar",
(
D("licenses", ("OOR",)),
D("metadata", ("layout.conf",)),
),
),
),
),
D(
"private-overlays",
(
D(
"overlay-foo-private",
(
D(
"category",
(D("ccl-pkg", ("ccl-pkg-1.ebuild",)),),
),
D("metadata", ("layout.conf",)),
),
),
D(
"chromeos-overlay",
(
D("licenses", ("CCL",)),
D("metadata", ("layout.conf",)),
),
),
D(
"chromeos-partner-overlay",
(
D("licenses", ("CPL",)),
D("metadata", ("layout.conf",)),
),
),
),
),
D(
"third_party",
(
D(
"chromiumos-overlay",
(
D("licenses", ("PUB",)),
D("metadata", ("layout.conf",)),
),
),
D(
"portage-stable",
(
D("licenses", ("TPL",)),
D("metadata", ("layout.conf",)),
),
),
),
),
),
),
D(
"build_infos",
(
D(
"metapackage-with-files",
(
D(
"build-info",
("CATEGORY", "CONTENTS", "LICENSE", "PF"),
),
),
),
D(
"metapackage-valid",
(
D(
"build-info",
("CATEGORY", "CONTENTS", "LICENSE", "PF"),
),
),
),
),
),
)
cros_test_lib.CreateOnDiskHierarchy(self.tempdir, file_layout)
# Write out the files declared above.
for lic in self.licenses.values():
osutils.WriteFile(
os.path.join(self.tempdir, lic["dir"]), lic["contents"]
)
for overlay in overlays:
content = layout_template % {
"masters": " ".join(overlay["masters"]),
"repo_name": overlay["repo-name"],
}
file_loc = os.path.join(
self.tempdir, overlay["dir"], "metadata", "layout.conf"
)
osutils.WriteFile(file_loc, content)
for _, ebuild in self.ebuilds.items():
content = ebuild_template % {"license": ebuild["license"]}
ebuild["content"] = content
osutils.WriteFile(
os.path.join(self.tempdir, ebuild["dir"]), content
)
for pkg, build_info in self.build_infos.items():
for filename, content in build_info["files"].items():
osutils.WriteFile(
os.path.join(
self.tempdir, "build_infos", pkg, "build-info", filename
),
content,
)
def testGetLicenseTypesFromEbuild(self):
"""Tests the fetched license from ebuilds are correct."""
ebuild_content = self.ebuilds["ftl-pkg"]["content"]
overlay_path = os.sep.join(
self.ebuilds["ftl-pkg"]["dir"].split(os.sep)[:-3]
)
result = licenses_lib.GetLicenseTypesFromEbuild(
ebuild_content, overlay_path, self.tempdir
)
expected = ["FTL"]
self.assertEqual(expected, sorted(result))
def testGetLicenseTypeFromEbuildTainted(self):
"""Tests the fetched tainted license from ebuilds is correct."""
ebuild_content = self.ebuilds["ttl-pkg"]["content"]
overlay_path = os.sep.join(
self.ebuilds["ttl-pkg"]["dir"].split(os.sep)[:-3]
)
result = licenses_lib.GetLicenseTypesFromEbuild(
ebuild_content, overlay_path, self.tempdir
)
expected = [licenses_lib.TAINTED]
self.assertEqual(expected, sorted(result))
def testFindLicenseType(self):
"""Tests the type for licenses are correctly identified.
e.g. gentoo vs custom.
"""
# Doesn't exist anywhere.
self.assertRaises(
AssertionError,
licenses_lib.Licensing.FindLicenseType,
"DoesNotExist",
buildroot=self.tempdir,
)
# Not in overlay hierarchy.
self.assertRaises(
AssertionError,
licenses_lib.Licensing.FindLicenseType,
"OOR",
board="foo",
buildroot=self.tempdir,
)
# Not included in the default search paths.
self.assertRaises(
AssertionError,
licenses_lib.Licensing.FindLicenseType,
"FTL",
buildroot=self.tempdir,
)
# Checking each license type is the expected.
for name, lic in self.licenses.items():
if lic.get("skip_test", False):
continue
result = licenses_lib.Licensing.FindLicenseType(
name, board=lic["board"], buildroot=self.tempdir
)
self.assertEqual(lic["type"], result)
def testReadSharedLicense(self):
"""Tests the license text is correctly fetched."""
# Doesn't exist.
self.assertRaises(
AssertionError,
licenses_lib.Licensing.ReadSharedLicense,
"DoesNotExist",
buildroot=self.tempdir,
)
# Not in overlay hierarchy.
self.assertRaises(
AssertionError,
licenses_lib.Licensing.ReadSharedLicense,
"OOR",
board="foo",
buildroot=self.tempdir,
)
# Not included in the default search paths.
self.assertRaises(
AssertionError,
licenses_lib.Licensing.ReadSharedLicense,
"FTL",
buildroot=self.tempdir,
)
for name, lic in self.licenses.items():
if lic.get("skip_test", False):
continue
result = licenses_lib.Licensing.ReadSharedLicense(
name, board=lic["board"], buildroot=self.tempdir
)
self.assertEqual(lic["contents"], result)
def testReadUnknownEncodedFile(self):
"""Validate the fix for crbug.com/654894."""
bad_license = os.path.join(self.tempdir, "license.rtf")
osutils.WriteFile(bad_license, "Foo\x00Bar")
result = licenses_lib.ReadUnknownEncodedFile(bad_license)
self.assertEqual(result, "FooBar")
def testDeprecatedLicenses(self):
"""Verify deprecated license checks."""
# These are known bad packages.
licenses_lib._CheckForKnownBadLicenses(
"chromeos-base/google-sans-fonts-1-r13", {"Google-TOS"}
)
# These packages should not be allowed.
with self.assertRaises(licenses_lib.PackageLicenseError):
licenses_lib._CheckForKnownBadLicenses(
"sys-apps/portage-123", {"Proprietary-Binary"}
)
with self.assertRaises(licenses_lib.PackageLicenseError):
licenses_lib._CheckForKnownBadLicenses(
"sys-apps/portage-123", {"GPL-2", "Google-TOS"}
)
def testHookPackageProcess(self):
build_infos_path = os.path.join(self.tempdir, "build_infos")
for pkg, build_info in self.build_infos.items():
if build_info["expected_exception"]:
with self.assertRaises(licenses_lib.PackageCorrectnessError):
licenses_lib.HookPackageProcess(
os.path.join(build_infos_path, pkg)
)
else:
licenses_lib.HookPackageProcess(
os.path.join(build_infos_path, pkg)
)
@mock.patch("chromite.lib.cros_build_lib.run")
def testListInstalledPackages(self, run_mock):
result = "[ U ] test to /build/test_dir\n[ U ] test2 to /build/test_dir"
run_mock.return_value = cros_build_lib.CompletedProcess(
args=[], returncode=0, stdout=result
)
self.assertRaisesRegex(
AssertionError,
r"^.*\[ U \] test.*\[ U \] test2.*$",
licenses_lib.ListInstalledPackages,
"",
)
result = "[ R ] test to /build/test_dir\n[ R ] test2 to /build/test_dir"
run_mock.return_value = cros_build_lib.CompletedProcess(
args=[], returncode=0, stdout=result
)
self.assertEqual(
licenses_lib.ListInstalledPackages(""), ["test", "test2"]
)
def testBannedLicenses(self):
"""Verify banned license checks."""
# These are somewhat redundant, but we want to be overly cautious.
# All of these have been used in Gentoo at some point.
BAD_LICENSES = {
"AGPL",
"AGPL-1",
"AGPL-2",
"AGPL-2+",
"AGPL-3",
"AGPL-3+",
"as-is",
"CC-BY-NC-4.0",
"CC-BY-NC-ND-2.0",
"CC-BY-NC-ND-2.5",
"CC-BY-NC-ND-3.0",
"CC-BY-NC-SA-2.5",
"CC-BY-NC-SA-3.0",
"CC-BY-NC-SA-4.0",
"CPAL",
"CPAL-1.0",
"EUPL",
"EUPL-1.1",
"WTFPL",
"WTFPL-2", # nocheck
}
for lic in BAD_LICENSES:
with self.assertRaises(licenses_lib.PackageLicenseError):
licenses_lib._CheckForKnownBadLicenses("sys-libs/db-18", {lic})
with self.assertRaises(licenses_lib.PackageLicenseError):
licenses_lib._CheckForKnownBadLicenses(
"sys-libs/db-18", {"GPL-2", lic}
)
def testYamlToJson(self):
"""Verify the migration logic."""
yaml_file = self.tempdir / "foo.yaml"
yaml_file.write_text(
"""
- !!python/tuple [sysroot, null]
- !!python/tuple [category, dev-python]
- !!python/tuple [name, pycryptodome]
- !!python/tuple [version, 3.7.3]
- !!python/tuple [revision, 0]
- !!python/tuple [fullname, dev-python/pycryptodome]
- !!python/tuple [fullnamerev, dev-python/pycryptodome-3.7.3]
- !!python/tuple
- license_names
- !!set {Unlicense: null}
- !!python/tuple
- license_text_scanned
- ["Scanned Source License \
pycryptodome-3.7.3-python3_6/pycryptodome.egg-info/PKG-INFO:\\n\\
Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*\\n"]
- !!python/tuple
- homepages
- ['https://www.pycryptodome.org',
'https://github.com/Legrandin/pycryptodome',
'https://pypi.org/project/pycryptodome/']
- !!python/tuple [skip, false]
- !!python/tuple [tainted, false]
- !!python/tuple [ebuild_path, null]
""",
encoding="utf-8",
)
json_file = self.tempdir / "foo.json"
licenses_lib._convert_yaml_to_json(yaml_file, json_file)
data = json.loads(json_file.read_bytes())
self.assertEqual(
data,
{
"category": "dev-python",
"ebuild_path": None,
"fullname": "dev-python/pycryptodome",
"fullnamerev": "dev-python/pycryptodome-3.7.3",
"homepages": [
"https://www.pycryptodome.org",
"https://github.com/Legrandin/pycryptodome",
"https://pypi.org/project/pycryptodome/",
],
"license_names": ["Unlicense"],
"license_text_scanned": [
"Scanned Source License "
"pycryptodome-3.7.3-python3_6/pycryptodome.egg-info/"
"PKG-INFO:\n"
"Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.*, "
"!=3.3.*\n"
],
"name": "pycryptodome",
"revision": 0,
"skip": False,
"sysroot": None,
"tainted": False,
"version": "3.7.3",
},
)