blob: af72677f8f72fc9d3f708702b1b17f7bdbeeaf64 [file] [log] [blame]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for Collector."""
from collections import defaultdict
import dataclasses
from gn_license_metadata import (
GnLicenseMetadata,
GnLicenseMetadataDB,
GnApplicableLicensesMetadata,
)
from pathlib import Path
from typing import Dict, List, Tuple
from file_access import FileAccess
from gn_label import GnLabel
from readme_fuchsia import ReadmesDB
from collector import Collector, CollectorError, CollectorErrorKind
import unittest
import tempfile
class CollectorTest(unittest.TestCase):
temp_dir: tempfile.TemporaryDirectory
temp_dir_path: Path
golibs_vendor_path: Path
collector: Collector
metadata_db: GnLicenseMetadataDB
def setUp(self) -> None:
self.temp_dir = tempfile.TemporaryDirectory()
self.temp_dir_path = Path(self.temp_dir.name)
self.golibs_vendor_path = (
self.temp_dir_path / "third_party" / "golibs" / "vendor"
)
self.golibs_vendor_path.mkdir(parents=True)
file_access = FileAccess(self.temp_dir_path)
self.metadata_db = GnLicenseMetadataDB.from_json_list([])
self.collector = Collector(
file_access=file_access,
metadata_db=self.metadata_db,
readmes_db=ReadmesDB(file_access),
include_host_tools=False,
)
return super().setUp()
def tearDown(self) -> None:
self.temp_dir.cleanup()
return super().tearDown()
def _collect_and_assert_licenses(
self, expected_names_and_licenses: Dict[str, List[str]]
):
self.collector.collect()
errors = set([e.kind for e in self.collector.errors])
self.assertSetEqual(errors, set(), msg="No errors are expected")
# Convert expected into a set of (name, license) tuples
expected = set()
for name in expected_names_and_licenses.keys():
for lic in expected_names_and_licenses[name]:
expected.add((name, lic))
# Convert actual into a set of (name, license) tuples
actual = set()
for collected_license in self.collector.unique_licenses:
for lic in collected_license.license_files:
actual.add((collected_license.public_name, str(lic)))
self.maxDiff = None
self.assertSetEqual(actual, expected)
def _collect_and_assert_errors_and_targets(
self, expected_targets_by_error: Dict[CollectorErrorKind, str]
):
self.collector.collect()
actual = {}
for error in self.collector.errors:
actual[error.kind] = str(error.target_label)
self.assertDictEqual(
actual,
expected_targets_by_error,
# Adding custom message since assert diff for dicts is hard to read
msg=f"Actual {actual} and expected {expected_targets_by_error} are different",
)
def _collect_and_assert_errors(
self, expected_error_kinds: List[CollectorErrorKind]
):
self.collector.collect()
actual = [e.kind for e in self.collector.errors]
self.assertSetEqual(set(actual), set(expected_error_kinds))
def _add_license_metadata(self, target: str, name: str, files: List[str]):
target_label = GnLabel.from_str(target)
self.metadata_db.add_license_metadata(
GnLicenseMetadata(
target_label=target_label,
public_package_name=name,
license_files=tuple(
[target_label.create_child_from_str(s) for s in files]
),
)
)
def _add_applicable_licenses_metadata(
self,
target: str,
licenses: List[str],
target_type="action",
third_party_resources: List[str] = None,
):
target_label = GnLabel.from_str(target)
if not third_party_resources:
third_party_resources = []
self.metadata_db.add_applicable_licenses_metadata(
application=GnApplicableLicensesMetadata(
target_label=target_label,
target_type=target_type,
license_labels=tuple([GnLabel.from_str(s) for s in licenses]),
third_party_resources=tuple(
[
target_label.create_child_from_str(s)
for s in third_party_resources
]
),
)
)
def _add_files(self, file_paths: List[str], base_path: Path = None):
"""Creates fake files in the temp dir"""
if not base_path:
base_path = self.temp_dir_path
else:
assert base_path.is_relative_to(self.temp_dir_path)
for path in file_paths:
path = base_path / path
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("")
########################### metadata tests:
def test_target_with_metadata(self):
self._add_license_metadata(
target="//third_party/foo:license",
name="Foo",
files=["license.txt"],
)
self._add_applicable_licenses_metadata(
target="//third_party/foo", licenses=["//third_party/foo:license"]
)
self._collect_and_assert_licenses(
{"Foo": ["//third_party/foo/license.txt"]}
)
def test_3p_target_with_metadata_but_no_applicable_licenses(self):
self._add_applicable_licenses_metadata(
target="//third_party/foo", licenses=[]
)
self._collect_and_assert_errors(
[CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES]
)
def test_prebuilt_target_with_metadata_but_no_applicable_licenses(self):
self._add_applicable_licenses_metadata(
target="//prebuilt/foo", licenses=[]
)
self._collect_and_assert_errors(
[CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES]
)
def test_target_with_metadata_but_no_such_license_label(self):
self._add_applicable_licenses_metadata(
target="//third_party/foo", licenses=["//third_party/foo:license"]
)
self._collect_and_assert_errors(
[CollectorErrorKind.APPLICABLE_LICENSE_REFERENCE_DOES_NOT_EXIST]
)
def test_non_third_party_target_without_licenses_does_not_error(self):
self._add_applicable_licenses_metadata(target="//foo/bar", licenses=[])
self._collect_and_assert_licenses({})
def test_non_third_party_target_with_license(self):
self._add_license_metadata(
target="//foo/bar:license", name="Bar", files=["license.txt"]
)
self._add_applicable_licenses_metadata(
target="//foo/bar", licenses=["//foo/bar:license"]
)
self._collect_and_assert_licenses({"Bar": ["//foo/bar/license.txt"]})
########################### readme tests:
def _add_readme_file(
self, file_path: str, name: str, license_files: List[str]
):
path = self.temp_dir_path / file_path
content = []
if name:
content.append(f"NAME: {name}")
for l in license_files:
content.append(f"LICENSE FILE: {l}")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("\n".join(content))
def test_target_with_readme(self):
self._add_applicable_licenses_metadata(
target="//third_party/foo", licenses=[]
)
self._add_files(["third_party/foo/license.txt"])
self._add_readme_file(
"third_party/foo/README.fuchsia",
name="Foo",
license_files=["license.txt"],
)
self._collect_and_assert_licenses(
{"Foo": ["//third_party/foo/license.txt"]}
)
def test_target_with_readme_without_license_specified(self):
target = "//third_party/foo"
self._add_applicable_licenses_metadata(target=target, licenses=[])
self._add_readme_file(
"third_party/foo/README.fuchsia", name="Foo", license_files=[]
)
self._collect_and_assert_errors(
[
CollectorErrorKind.NO_LICENSE_FILE_IN_README,
CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES,
]
)
def test_target_with_readme_but_license_file_not_found(self):
target = "//third_party/foo"
self._add_applicable_licenses_metadata(target=target, licenses=[])
self._add_readme_file(
"third_party/foo/README.fuchsia",
name="Foo",
license_files=["missing_file"],
)
self._collect_and_assert_errors(
[
CollectorErrorKind.LICENSE_FILE_IN_README_NOT_FOUND,
CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES,
]
)
def test_target_with_readme_but_license_name_missing_is_ok(self):
target = "//third_party/foo"
self._add_applicable_licenses_metadata(target=target, licenses=[])
self._add_files(["third_party/foo/license.txt"])
self._add_readme_file(
"third_party/foo/README.fuchsia",
name=None,
license_files=["license.txt"],
)
self._collect_and_assert_errors(
[
CollectorErrorKind.NO_PACKAGE_NAME_IN_README,
CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES,
]
)
def test_3p_group_target_with_readme_but_license_name_missing(self):
target = "//third_party/foo"
self._add_applicable_licenses_metadata(
target=target, licenses=[], target_type="group"
)
self._add_files(["third_party/foo/license.txt"])
self._add_readme_file(
"third_party/foo/README.fuchsia",
name=None,
license_files=["license.txt"],
)
self._collect_and_assert_licenses({})
def test_3p_group_target_with_3p_resource_and_readme_but_license_name_missing(
self,
):
target = "//third_party/foo"
resource = "//third_party/bar/baz"
self._add_applicable_licenses_metadata(
target=target,
licenses=[],
target_type="group",
third_party_resources=[resource],
)
self._add_files(["third_party/foo/license.txt"])
self._add_readme_file(
"third_party/foo/README.fuchsia",
name=None,
license_files=["license.txt"],
)
self._collect_and_assert_errors_and_targets(
{
CollectorErrorKind.NO_PACKAGE_NAME_IN_README: target,
CollectorErrorKind.THIRD_PARTY_RESOURCE_WITHOUT_LICENSE: resource,
CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES: target,
}
)
def test_non_3p_group_target_with_3p_resource_and_readme_but_license_name_missing(
self,
):
target = "//foo"
resource = "//third_party/bar/baz"
self._add_applicable_licenses_metadata(
target=target,
licenses=[],
target_type="group",
third_party_resources=[resource],
)
self._add_files(["third_party/foo/license.txt"])
self._add_readme_file(
"third_party/foo/README.fuchsia",
name=None,
license_files=["license.txt"],
)
self._collect_and_assert_errors_and_targets(
{
CollectorErrorKind.THIRD_PARTY_RESOURCE_WITHOUT_LICENSE: resource,
}
)
########################### resources tests:
def test_target_with_3p_resource_without_licenses(self):
target = "//foo"
self._add_applicable_licenses_metadata(
target=target,
licenses=[],
third_party_resources=["//third_party/bar/baz"],
)
self._add_files(["third_party/bar/license.txt"])
self._add_readme_file(
"third_party/bar/README.fuchsia",
name="bar",
license_files=["license.txt"],
)
self._collect_and_assert_licenses(
{"bar": ["//third_party/bar/license.txt"]}
)
def test_3p_target_with_3p_resource_and_no_license_but_resource_has_license(
self,
):
target = "//third_party/foo"
self._add_applicable_licenses_metadata(
target=target,
licenses=[],
third_party_resources=["//third_party/bar/baz"],
)
self._add_files(["third_party/bar/license.txt"])
self._add_readme_file(
"third_party/bar/README.fuchsia",
name="bar",
license_files=["license.txt"],
)
self._collect_and_assert_errors(
[CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES]
)
def test_3p_group_target_with_3p_resource_and_no_license_but_resource_has_license(
self,
):
target = "//third_party/foo"
self._add_applicable_licenses_metadata(
target=target,
licenses=[],
target_type="group",
third_party_resources=["//third_party/bar/baz"],
)
self._add_files(["third_party/bar/license.txt"])
self._add_readme_file(
"third_party/bar/README.fuchsia",
name="bar",
license_files=["license.txt"],
)
self._collect_and_assert_licenses(
{"bar": ["//third_party/bar/license.txt"]}
)
def test_3p_target_with_3p_resource_with_different_licenses(self):
target = "//third_party/foo"
self._add_license_metadata(
target="//third_party/foo:license",
name="foo",
files=["//third_party/foo/license.txt"],
)
self._add_applicable_licenses_metadata(
target=target,
licenses=["//third_party/foo:license"],
third_party_resources=["//third_party/bar/baz"],
)
self._add_files(["third_party/foo/license.txt"])
self._add_files(["third_party/bar/license.txt"])
self._add_readme_file(
"third_party/bar/README.fuchsia",
name="bar",
license_files=["license.txt"],
)
self._collect_and_assert_licenses(
{
"bar": ["//third_party/bar/license.txt"],
"foo": ["//third_party/foo/license.txt"],
}
)
def test_3p_group_target_without_resources_or_licenses(self):
self._add_applicable_licenses_metadata(
target="//third_party/foo", target_type="group", licenses=[]
)
# groupless 3p groups don't need a license: No errors.
self._collect_and_assert_licenses({})
########################### golib tests:
def _add_golib_vendor_files(self, file_paths: List[str]):
self._add_files(file_paths, base_path=self.golibs_vendor_path)
def test_golib_license_simple(self):
self._add_applicable_licenses_metadata(
target="//third_party/golibs:foo/bar", licenses=[]
)
self._add_golib_vendor_files(["foo/bar/lib.go", "foo/bar/LICENSE"])
self._collect_and_assert_licenses(
{"bar": ["//third_party/golibs/vendor/foo/bar/LICENSE"]}
)
def test_golib_license_simple(self):
self._add_applicable_licenses_metadata(
target="//third_party/golibs:foo/bar", licenses=[]
)
self._add_golib_vendor_files(["foo/bar/lib.go", "foo/bar/LICENSE"])
self._collect_and_assert_licenses(
{"bar": ["//third_party/golibs/vendor/foo/bar/LICENSE"]}
)
def test_golib_license_when_parent_dir_has_the_license(self):
self._add_applicable_licenses_metadata(
target="//third_party/golibs:foo/bar", licenses=[]
)
self._add_golib_vendor_files(["foo/bar/lib.go", "foo/LICENSE"])
self._collect_and_assert_licenses(
{"foo": ["//third_party/golibs/vendor/foo/LICENSE"]}
)
def test_golib_license_when_child_dir_has_the_license(self):
self._add_applicable_licenses_metadata(
target="//third_party/golibs:foo", licenses=[]
)
self._add_golib_vendor_files(["foo/bar/lib.go", "foo/bar/baz/LICENSE"])
self._collect_and_assert_licenses(
{"foo": ["//third_party/golibs/vendor/foo/bar/baz/LICENSE"]}
)
def test_golib_license_with_different_names_of_license_files(self):
self._add_applicable_licenses_metadata(
target="//third_party/golibs:foo", licenses=[]
)
self._add_golib_vendor_files(
[
"foo/lib.go", # Not a license
"foo/license",
"foo/LICENSE-MIT",
"foo/LICENSE.txt",
"foo/COPYRIGHT",
"foo/NOTICE",
"foo/COPYING", # Not a license
]
)
self._collect_and_assert_licenses(
{
"foo": [
"//third_party/golibs/vendor/foo/license",
"//third_party/golibs/vendor/foo/LICENSE-MIT",
"//third_party/golibs/vendor/foo/LICENSE.txt",
"//third_party/golibs/vendor/foo/COPYRIGHT",
"//third_party/golibs/vendor/foo/NOTICE",
]
}
)
def test_golib_without_license(self):
target = "//third_party/golibs:foo"
self._add_applicable_licenses_metadata(target=target, licenses=[])
self._add_golib_vendor_files(["foo/lib.go"])
self._collect_and_assert_errors(
[
CollectorErrorKind.THIRD_PARTY_GOLIB_WITHOUT_LICENSES,
CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES,
]
)
########################### Default license:
def test_adds_default_license_for_non_3p_target(self):
self._add_files(["default_license.txt"])
self.collector = dataclasses.replace(
self.collector,
default_license_file=GnLabel.from_str("//default_license.txt"),
)
self._add_applicable_licenses_metadata(target="//foo", licenses=[])
self._collect_and_assert_licenses(
{"Fuchsia": ["//default_license.txt"]}
)
def test_does_not_add_default_license_for_3p_target(self):
self._add_files(["default_license.txt"])
self.collector = dataclasses.replace(
self.collector,
default_license_file=GnLabel.from_str("//default_license.txt"),
)
self._add_applicable_licenses_metadata(
target="//third_party/foo", licenses=[]
)
self._collect_and_assert_errors(
[CollectorErrorKind.THIRD_PARTY_TARGET_WITHOUT_APPLICABLE_LICENSES]
)
########################### Host targets:
def _add_host_tool_target_and_licenses(self):
self._add_files(["third_party/foo/license.txt"])
self._add_license_metadata(
target="//third_party/foo:license",
name="Foo",
files=["license.txt"],
)
self._add_applicable_licenses_metadata(
target="//third_party/foo(//host_toolchain)",
licenses=["//third_party/foo:license"],
)
def test_includes_host_tools(self):
self._add_host_tool_target_and_licenses()
self.collector = dataclasses.replace(
self.collector, include_host_tools=True
)
self._collect_and_assert_licenses(
{"Foo": ["//third_party/foo/license.txt"]}
)
def test_excludes_host_tools(self):
self._add_host_tool_target_and_licenses()
self.collector = dataclasses.replace(
self.collector, include_host_tools=False
)
self._collect_and_assert_licenses({})
#################### Ignored licenses
def test_ignores_the_no_license_label(self):
self._add_applicable_licenses_metadata(
target="//third_party/foo",
licenses=["//build/licenses:no_license(//some:toolchain)"],
)
self._collect_and_assert_licenses({})
if __name__ == "__main__":
unittest.main()