blob: cb0221e0b2b85a529fb24e9c9223411a3d87c57b [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2017 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for //tools/licenses/licenses.py.
"""
import csv
import io
import os
import pathlib
import sys
import unittest
from unittest import mock
REPOSITORY_ROOT = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..'))
sys.path.append(os.path.join(REPOSITORY_ROOT, 'tools', 'licenses'))
import licenses
from test_utils import path_from_root
def construct_absolute_path(path):
return str(pathlib.PurePosixPath(REPOSITORY_ROOT.replace(os.sep, '/'), path))
class LicensesTest(unittest.TestCase):
def _get_metadata(self):
return {
os.path.join('third_party', 'lib1'): [{
'Name': 'lib1',
'Shipped': 'yes',
'License': 'MIT',
'License File': [os.path.join('third_party', 'lib1', 'LICENSE')],
}],
os.path.join('third_party', 'lib2'): [{
'Name': 'lib2',
'Shipped': 'yes',
'License': 'MIT, Apache 2.0',
'License File': [
os.path.join('third_party', 'lib2', 'LICENSE-A'),
os.path.join('third_party', 'lib2', 'LICENSE-B'),
],
}],
'ignored': [{
'Name': 'ignored',
'Shipped': 'no',
'License File': [],
}],
os.path.join('third_party', 'lib_unshipped'): [{
'Name': 'lib_unshipped',
'Shipped': 'no',
'License': '',
'License File': [
os.path.join('third_party', 'lib_unshipped', 'LICENSE')
],
}],
os.path.join('third_party', 'lib3'): [{
'Name': 'lib3',
'Shipped': 'yes',
'License': '',
'License File': [os.path.join('third_party', 'lib3', 'LICENSE')],
}],
os.path.join('third_party', 'lib3-v1'): [{
# Test SPDX license file dedup. (different name, same license file)
'Name': 'lib3-v1',
'Shipped': 'yes',
'License': 'Apache 2.0',
'License File': [
os.path.join('third_party', 'lib3', 'LICENSE'),
],
}],
os.path.join('third_party', 'lib3-v2'): [{
# Test SPDX id dedup. (same name, different license file)
'Name': 'lib3',
'Shipped': 'yes',
'License': 'BSD',
'License File': [os.path.join('third_party', 'lib3-v2', 'LICENSE')],
}],
}
def test_parse_dir(self):
# No metadata file found in directory
test_path = os.path.join('tools', 'licenses', 'foo')
with self.assertRaisesRegex(
licenses.LicenseError,
"missing third party metadata file or licenses.py SPECIAL_CASES"):
licenses.ParseDir(test_path, REPOSITORY_ROOT)
test_path = os.path.join('tools', 'licenses', 'test_dir')
dir_metadata, errors = licenses.ParseDir(test_path, REPOSITORY_ROOT)
expected = [
{
'License File': [
os.path.join(REPOSITORY_ROOT, test_path, 'LICENSE')
],
'Name': 'License tools directory parsing test',
'URL': 'https://chromium.tools.licenses.test/src.git',
'License': 'FAKE',
'Shipped': 'no',
},
{
'License File': [
os.path.join(REPOSITORY_ROOT, test_path, 'LICENSE')
],
'Name': 'License tools directory parsing test for multiple',
'URL': 'https://chromium.tools.licenses.test/multi/src.git',
'License': 'FAKE',
'Shipped': 'no',
},
]
self.assertListEqual(errors, [])
self.assertListEqual(dir_metadata, expected)
def test_get_third_party_deps_from_gn_deps_output(self):
prune_path = next(iter(licenses.PRUNE_PATHS))
gn_deps = [
construct_absolute_path('net/BUILD.gn'),
construct_absolute_path('third_party/zlib/BUILD.gn'),
construct_absolute_path('third_party/cld_3/src/src/BUILD.gn'),
construct_absolute_path(prune_path + '/BUILD.gn'),
construct_absolute_path('external/somelib/BUILD.gn'),
]
third_party_deps = licenses.GetThirdPartyDepsFromGNDepsOutput(
'\n'.join(gn_deps), None)
# 'net' is not in the output because it's not a third_party dependency.
#
# It must return the direct sub-directory of "third_party". So it should
# return 'third_party/cld_3', not 'third_party/cld_3/src/src'.
self.assertEqual(
third_party_deps,
set([
os.path.join('third_party', 'zlib'),
os.path.join('third_party', 'cld_3'),
]))
def test_get_third_party_deps_from_gn_deps_output_extra_dirs(self):
prune_path = next(iter(licenses.PRUNE_PATHS))
gn_deps = [
construct_absolute_path('net/BUILD.gn'),
construct_absolute_path('third_party/zlib/BUILD.gn'),
construct_absolute_path('third_party/cld_3/src/src/BUILD.gn'),
construct_absolute_path(prune_path + '/BUILD.gn'),
construct_absolute_path('external/somelib/BUILD.gn'),
]
third_party_deps = licenses.GetThirdPartyDepsFromGNDepsOutput(
'\n'.join(gn_deps), None, ['external'])
self.assertEqual(
third_party_deps,
set([
os.path.join('third_party', 'zlib'),
os.path.join('third_party', 'cld_3'),
os.path.join('external', 'somelib'),
]))
def test_generate_license_file_csv(self):
# This is the same for all the links and prevents wildly long strings.
prefix = ("https://source.chromium.org/chromium/chromium/src/+/main:")
csv_file = io.StringIO(licenses.GenerateLicenseFileCsv(
self._get_metadata()))
csv_rows = [row for row in csv.DictReader(csv_file)]
expected = [{
'Library Name': 'Chromium',
'Link to LICENSE file': f'{prefix}LICENSE',
'License Name': 'BSD 3-Clause',
'Binary which uses library': 'Chromium',
'License text for library included?': 'Yes',
'Source code for library includes the mirrored source?': 'No',
'Authorization date': 'N/A'
}, {
'Library Name': 'lib1',
'Link to LICENSE file': (
f'{prefix}tools/licenses/third_party/lib1/LICENSE'),
'License Name': 'MIT',
'Binary which uses library': 'Chromium',
'License text for library included?': 'Yes',
'Source code for library includes the mirrored source?': 'No',
'Authorization date': 'N/A'
}, {
'Library Name': 'lib2',
'Link to LICENSE file': (
f'{prefix}tools/licenses/third_party/lib2/LICENSE-A, '
f'{prefix}tools/licenses/third_party/lib2/LICENSE-B'),
'License Name': 'MIT, Apache 2.0',
'Binary which uses library': 'Chromium',
'License text for library included?': 'Yes',
'Source code for library includes the mirrored source?': 'No',
'Authorization date': 'N/A'
}, {
'Library Name': 'lib3',
'Link to LICENSE file': (
f'{prefix}tools/licenses/third_party/lib3/LICENSE'),
'License Name': 'UNKNOWN',
'Binary which uses library': 'Chromium',
'License text for library included?': 'Yes',
'Source code for library includes the mirrored source?': 'No',
'Authorization date': 'N/A'
}, {
'Library Name': 'lib3-v1',
'Link to LICENSE file': (
f'{prefix}tools/licenses/third_party/lib3/LICENSE'),
'License Name': 'Apache 2.0',
'Binary which uses library': 'Chromium',
'License text for library included?': 'Yes',
'Source code for library includes the mirrored source?': 'No',
'Authorization date': 'N/A'
}, {
'Library Name': 'lib3',
'Link to LICENSE file': (
f'{prefix}tools/licenses/third_party/lib3-v2/LICENSE'),
'License Name': 'BSD',
'Binary which uses library': 'Chromium',
'License text for library included?': 'Yes',
'Source code for library includes the mirrored source?': 'No',
'Authorization date': 'N/A'
}]
expected.sort(key=lambda item: item['Library Name'])
self.assertEqual(csv_rows, expected)
def test_generate_license_file_txt(self):
read_file_vals = [
'root license text\n',
'lib1 license text\n',
'lib2-a license text\n',
'lib2-b license text\n',
'lib3 license text\n',
'lib3 license text\n',
'lib3-v2 license text\n',
]
license_txt = licenses.GenerateLicenseFilePlainText(
self._get_metadata(), read_file=lambda _: read_file_vals.pop(0))
expected = '\n'.join([
'root license text',
'',
'--------------------',
'lib1',
'--------------------',
'lib1 license text',
'',
'--------------------',
'lib2',
'--------------------',
'lib2-a license text',
'',
'lib2-b license text',
'',
'--------------------',
'lib3',
'--------------------',
'lib3 license text',
'',
'--------------------',
'lib3-v1',
'--------------------',
'lib3 license text',
'',
'--------------------',
'lib3',
'--------------------',
'lib3-v2 license text',
]) + '\n' # extra new line to account for join not adding one to the end
self.assertEqual(license_txt, expected)
def test_generate_license_file_sdpx(self):
read_file_vals = [
'root\nlicense text\n',
'lib1\nlicense text\n',
'lib2-a\nlicense text\n',
'lib2-b\nlicense text\n',
'lib3\nlicense text\n',
'lib3-v2\nlicense text\n',
]
license_txt = licenses.GenerateLicenseFileSpdx(
self._get_metadata(),
'http://google.com',
path_from_root('src'),
'mydoc',
'http://google.com',
repo_root=path_from_root('src'),
read_file=lambda _: read_file_vals.pop(0))
expected = '''{
"spdxVersion": "SPDX-2.2",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "mydoc",
"documentNamespace": "http://google.com",
"creationInfo": {
"creators": [
"Tool: spdx_writer.py"
]
},
"dataLicense": "CC0-1.0",
"documentDescribes": [
"SPDXRef-Package-Chromium"
],
"packages": [
{
"SPDXID": "SPDXRef-Package-Chromium",
"name": "Chromium",
"licenseConcluded": "LicenseRef-Chromium"
},
{
"SPDXID": "SPDXRef-Package-lib1",
"name": "lib1",
"licenseConcluded": "LicenseRef-lib1"
},
{
"SPDXID": "SPDXRef-Package-lib2",
"name": "lib2",
"licenseConcluded": "LicenseRef-lib2"
},
{
"SPDXID": "SPDXRef-Package-lib2-1",
"name": "lib2",
"licenseConcluded": "LicenseRef-lib2-1"
},
{
"SPDXID": "SPDXRef-Package-lib3",
"name": "lib3",
"licenseConcluded": "LicenseRef-lib3"
},
{
"SPDXID": "SPDXRef-Package-lib3-v1",
"name": "lib3-v1",
"licenseConcluded": "LicenseRef-lib3"
},
{
"SPDXID": "SPDXRef-Package-lib3-1",
"name": "lib3",
"licenseConcluded": "LicenseRef-lib3-1"
}
],
"hasExtractedLicensingInfos": [
{
"name": "Chromium",
"licenseId": "LicenseRef-Chromium",
"extractedText": "root\\nlicense text\\n",
"crossRefs": [
{
"url": "http://google.com/LICENSE"
}
]
},
{
"name": "lib1",
"licenseId": "LicenseRef-lib1",
"extractedText": "lib1\\nlicense text\\n",
"crossRefs": [
{
"url": "http://google.com/third_party/lib1/LICENSE"
}
]
},
{
"name": "lib2",
"licenseId": "LicenseRef-lib2",
"extractedText": "lib2-a\\nlicense text\\n",
"crossRefs": [
{
"url": "http://google.com/third_party/lib2/LICENSE-A"
}
]
},
{
"name": "lib2",
"licenseId": "LicenseRef-lib2-1",
"extractedText": "lib2-b\\nlicense text\\n",
"crossRefs": [
{
"url": "http://google.com/third_party/lib2/LICENSE-B"
}
]
},
{
"name": "lib3",
"licenseId": "LicenseRef-lib3",
"extractedText": "lib3\\nlicense text\\n",
"crossRefs": [
{
"url": "http://google.com/third_party/lib3/LICENSE"
}
]
},
{
"name": "lib3",
"licenseId": "LicenseRef-lib3-1",
"extractedText": "lib3-v2\\nlicense text\\n",
"crossRefs": [
{
"url": "http://google.com/third_party/lib3-v2/LICENSE"
}
]
}
],
"relationships": [
{
"spdxElementId": "SPDXRef-Package-Chromium",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-lib1"
},
{
"spdxElementId": "SPDXRef-Package-Chromium",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-lib2"
},
{
"spdxElementId": "SPDXRef-Package-Chromium",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-lib2-1"
},
{
"spdxElementId": "SPDXRef-Package-Chromium",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-lib3"
},
{
"spdxElementId": "SPDXRef-Package-Chromium",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-lib3-v1"
},
{
"spdxElementId": "SPDXRef-Package-Chromium",
"relationshipType": "CONTAINS",
"relatedSpdxElement": "SPDXRef-Package-lib3-1"
}
]
}'''
self.assertEqual(license_txt, expected)
if __name__ == '__main__':
unittest.main()