Skip to content

Commit

Permalink
gate LicenseConcluded behind a CLI flag
Browse files Browse the repository at this point in the history
  • Loading branch information
pietroalbini committed Nov 10, 2022
1 parent 094c396 commit 569a7e5
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 29 deletions.
23 changes: 18 additions & 5 deletions src/reuse/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@
class _MultiprocessingContainer:
"""Container that remembers some data in order to generate a FileReport."""

def __init__(self, project, do_checksum):
def __init__(self, project, do_checksum, add_license_concluded):
self.project = project
self.do_checksum = do_checksum
self.add_license_concluded = add_license_concluded

def __call__(self, file_):
# pylint: disable=broad-except
try:
return _MultiprocessingResult(
file_,
FileReport.generate(
self.project, file_, do_checksum=self.do_checksum
self.project,
file_,
do_checksum=self.do_checksum,
add_license_concluded=self.add_license_concluded,
),
None,
)
Expand Down Expand Up @@ -177,6 +181,7 @@ def generate(
project: Project,
do_checksum: bool = True,
multiprocessing: bool = cpu_count() > 1,
add_license_concluded: bool = False,
) -> "ProjectReport":
"""Generate a ProjectReport from a Project."""
project_report = cls(do_checksum=do_checksum)
Expand All @@ -186,7 +191,9 @@ def generate(
project.licenses_without_extension
)

container = _MultiprocessingContainer(project, do_checksum)
container = _MultiprocessingContainer(
project, do_checksum, add_license_concluded
)

if multiprocessing:
with mp.Pool() as pool:
Expand Down Expand Up @@ -338,7 +345,11 @@ def to_dict(self):

@classmethod
def generate(
cls, project: Project, path: PathLike, do_checksum: bool = True
cls,
project: Project,
path: PathLike,
do_checksum: bool = True,
add_license_concluded: bool = False,
) -> "FileReport":
"""Generate a FileReport from a path in a Project."""
path = Path(path)
Expand Down Expand Up @@ -379,7 +390,9 @@ def generate(
# Add license to report.
report.spdxfile.licenses_in_file.append(identifier)

if not spdx_info.spdx_expressions:
if not add_license_concluded:
report.spdxfile.license_concluded = "NOASSERTION"
elif not spdx_info.spdx_expressions:
report.spdxfile.license_concluded = "NONE"
else:
# Merge all the license expressions together, wrapping them in
Expand Down
12 changes: 11 additions & 1 deletion src/reuse/spdx.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ def add_arguments(parser) -> None:
parser.add_argument(
"--output", "-o", dest="file", action="store", type=PathType("w")
)
parser.add_argument(
"--add-license-concluded",
action="store_true",
help=_(
"Populate the LicenseConcluded field. Note that REUSE cannot "
"guarantee the field is accurate."
),
)


def run(args, project: Project, out=sys.stdout) -> int:
Expand All @@ -43,7 +51,9 @@ def run(args, project: Project, out=sys.stdout) -> int:
)

report = ProjectReport.generate(
project, multiprocessing=not args.no_multiprocessing
project,
multiprocessing=not args.no_multiprocessing,
add_license_concluded=args.add_license_concluded,
)

out.write(report.bill_of_materials())
Expand Down
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ def multiprocessing(request, monkeypatch) -> bool:
yield request.param


@pytest.fixture(params=[True, False])
def add_license_concluded(request) -> bool:
yield request


@pytest.fixture()
def empty_directory(tmpdir_factory) -> Path:
"""Create a temporary empty directory."""
Expand Down
22 changes: 21 additions & 1 deletion tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,30 @@ def test_spdx(fake_repository, stringio):
"""Compile to an SPDX document."""
os.chdir(str(fake_repository))
result = main(["spdx"], out=stringio)
output = stringio.getvalue()

# Ensure no LicenseConcluded is included without the flag
assert "\nLicenseConcluded: NOASSERTION\n" in output
assert "\nLicenseConcluded: GPL-3.0-or-later\n" not in output

# TODO: This test is rubbish.
assert result == 0
assert stringio.getvalue()
assert output


def test_spdx_add_license_concluded(fake_repository, stringio):
"""Compile to an SPDX document."""
os.chdir(str(fake_repository))
result = main(["spdx", "--add-license-concluded"], out=stringio)
output = stringio.getvalue()

# Ensure no LicenseConcluded is included without the flag
assert "\nLicenseConcluded: NOASSERTION\n" not in output
assert "\nLicenseConcluded: GPL-3.0-or-later\n" in output

# TODO: This test is rubbish.
assert result == 0
assert output


def test_spdx_no_multiprocessing(fake_repository, stringio, multiprocessing):
Expand Down
108 changes: 86 additions & 22 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,64 +28,101 @@
# REUSE-IgnoreStart


def test_generate_file_report_file_simple(fake_repository):
def test_generate_file_report_file_simple(
fake_repository, add_license_concluded
):
"""An extremely simple generate test, just to see if the function doesn't
crash.
"""
project = Project(fake_repository)
result = FileReport.generate(project, "src/source_code.py")
result = FileReport.generate(
project,
"src/source_code.py",
add_license_concluded=add_license_concluded,
)

assert result.spdxfile.licenses_in_file == ["GPL-3.0-or-later"]
assert result.spdxfile.license_concluded == "GPL-3.0-or-later"
assert (
result.spdxfile.license_concluded == "GPL-3.0-or-later"
if add_license_concluded
else "NOASSERTION"
)
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Jane Doe"
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_file_from_different_cwd(fake_repository):
def test_generate_file_report_file_from_different_cwd(
fake_repository, add_license_concluded
):
"""Another simple generate test, but from a different CWD."""
os.chdir("/")
project = Project(fake_repository)
result = FileReport.generate(
project, fake_repository / "src/source_code.py"
project,
fake_repository / "src/source_code.py",
add_license_concluded=add_license_concluded,
)
assert result.spdxfile.licenses_in_file == ["GPL-3.0-or-later"]
assert result.spdxfile.license_concluded == "GPL-3.0-or-later"
assert (
result.spdxfile.license_concluded == "GPL-3.0-or-later"
if add_license_concluded
else "NOASSERTION"
)
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Jane Doe"
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_file_missing_license(fake_repository):
def test_generate_file_report_file_missing_license(
fake_repository, add_license_concluded
):
"""Simple generate test with a missing license."""
(fake_repository / "foo.py").write_text(
"SPDX-License-Identifier: BSD-3-Clause"
)
project = Project(fake_repository)
result = FileReport.generate(project, "foo.py")
result = FileReport.generate(
project, "foo.py", add_license_concluded=add_license_concluded
)

assert result.spdxfile.copyright == ""
assert result.spdxfile.licenses_in_file == ["BSD-3-Clause"]
assert result.spdxfile.license_concluded == "BSD-3-Clause"
assert (
result.spdxfile.license_concluded == "BSD-3-Clause"
if add_license_concluded
else "NOASSERTION"
)
assert result.missing_licenses == {"BSD-3-Clause"}
assert not result.bad_licenses


def test_generate_file_report_file_bad_license(fake_repository):
def test_generate_file_report_file_bad_license(
fake_repository, add_license_concluded
):
"""Simple generate test with a bad license."""
(fake_repository / "foo.py").write_text(
"SPDX-License-Identifier: fakelicense"
)
project = Project(fake_repository)
result = FileReport.generate(project, "foo.py")
result = FileReport.generate(
project, "foo.py", add_license_concluded=add_license_concluded
)

assert result.spdxfile.copyright == ""
assert result.spdxfile.licenses_in_file == ["fakelicense"]
assert result.spdxfile.license_concluded == "fakelicense"
assert (
result.spdxfile.license_concluded == "fakelicense"
if add_license_concluded
else "NOASSERTION"
)
assert result.bad_licenses == {"fakelicense"}
assert result.missing_licenses == {"fakelicense"}


def test_generate_file_report_license_contains_plus(fake_repository):
def test_generate_file_report_license_contains_plus(
fake_repository, add_license_concluded
):
"""Given a license expression akin to Apache-1.0+, LICENSES/Apache-1.0.txt
should be an appropriate license file.
"""
Expand All @@ -94,49 +131,74 @@ def test_generate_file_report_license_contains_plus(fake_repository):
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()
project = Project(fake_repository)
result = FileReport.generate(project, "foo.py")
result = FileReport.generate(
project, "foo.py", add_license_concluded=add_license_concluded
)

assert result.spdxfile.copyright == ""
assert result.spdxfile.licenses_in_file == ["Apache-1.0+"]
assert result.spdxfile.license_concluded == "Apache-1.0+"
assert (
result.spdxfile.license_concluded == "Apache-1.0+"
if add_license_concluded
else "NOASSERTION"
)
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_exception(fake_repository):
def test_generate_file_report_exception(fake_repository, add_license_concluded):
"""Simple generate test to test if the exception is detected."""
project = Project(fake_repository)
result = FileReport.generate(project, "src/exception.py")
result = FileReport.generate(
project, "src/exception.py", add_license_concluded=add_license_concluded
)

assert set(result.spdxfile.licenses_in_file) == {
"GPL-3.0-or-later",
"Autoconf-exception-3.0",
}
assert (
result.spdxfile.license_concluded
== "GPL-3.0-or-later WITH Autoconf-exception-3.0"
if add_license_concluded
else "NOASSERTION"
)
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Jane Doe"
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_no_licenses(fake_repository):
def test_generate_file_report_no_licenses(
fake_repository, add_license_concluded
):
"""Test behavior when no license information is present in the file"""
(fake_repository / "foo.py").write_text("")
project = Project(fake_repository)
result = FileReport.generate(project, "foo.py")
result = FileReport.generate(
project, "foo.py", add_license_concluded=add_license_concluded
)

assert result.spdxfile.copyright == ""
assert not result.spdxfile.licenses_in_file
assert result.spdxfile.license_concluded == "NONE"
assert (
result.spdxfile.license_concluded == "NONE"
if add_license_concluded
else "NOASSERTION"
)
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_multiple_licenses(fake_repository):
def test_generate_file_report_multiple_licenses(
fake_repository, add_license_concluded
):
"""Test that all licenses are included in LicenseConcluded"""
project = Project(fake_repository)
result = FileReport.generate(project, "src/multiple_licenses.rs")
result = FileReport.generate(
project,
"src/multiple_licenses.rs",
add_license_concluded=add_license_concluded,
)

assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2022 Jane Doe"
assert set(result.spdxfile.licenses_in_file) == {
Expand All @@ -149,6 +211,8 @@ def test_generate_file_report_multiple_licenses(fake_repository):
result.spdxfile.license_concluded
== "GPL-3.0-or-later AND (Apache-2.0 OR CC0-1.0"
" WITH Autoconf-exception-3.0)"
if add_license_concluded
else "NOASSERTION"
)
assert not result.bad_licenses
assert not result.missing_licenses
Expand Down

0 comments on commit 569a7e5

Please sign in to comment.