718 changes: 462 additions & 256 deletions tests/test_main_addheader.py

Large diffs are not rendered by default.

165 changes: 165 additions & 0 deletions tests/test_main_addheader_merge.py
@@ -0,0 +1,165 @@
# SPDX-FileCopyrightText: 2021 Liam Beguin <liambeguin@gmail.com>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Tests for reuse._main: addheader merge-copyrights option"""


from inspect import cleandoc

from reuse._main import main


def test_addheader_merge_copyrights_simple(fake_repository, stringio):
"""Add multiple headers to a file with merge copyrights."""
simple_file = fake_repository / "foo.py"
simple_file.write_text("pass")

result = main(
[
"addheader",
"--year",
"2016",
"--license",
"GPL-3.0-or-later",
"--copyright",
"Mary Sue",
"--merge-copyrights",
"foo.py",
],
out=stringio,
)

assert result == 0
assert (
simple_file.read_text()
== cleandoc(
"""
# spdx-FileCopyrightText: 2016 Mary Sue
#
# spdx-License-Identifier: GPL-3.0-or-later
pass
"""
).replace("spdx", "SPDX")
)

result = main(
[
"addheader",
"--year",
"2018",
"--license",
"GPL-3.0-or-later",
"--copyright",
"Mary Sue",
"--merge-copyrights",
"foo.py",
],
out=stringio,
)

assert result == 0
assert (
simple_file.read_text()
== cleandoc(
"""
# spdx-FileCopyrightText: 2016 - 2018 Mary Sue
#
# spdx-License-Identifier: GPL-3.0-or-later
pass
"""
).replace("spdx", "SPDX")
)


def test_addheader_merge_copyrights_multi_prefix(fake_repository, stringio):
"""Add multiple headers to a file with merge copyrights."""
simple_file = fake_repository / "foo.py"
simple_file.write_text("pass")

for i in range(0, 3):
result = main(
[
"addheader",
"--year",
str(2010 + i),
"--license",
"GPL-3.0-or-later",
"--copyright",
"Mary Sue",
"foo.py",
],
out=stringio,
)

assert result == 0

for i in range(0, 5):
result = main(
[
"addheader",
"--year",
str(2015 + i),
"--license",
"GPL-3.0-or-later",
"--copyright-style",
"string-c",
"--copyright",
"Mary Sue",
"foo.py",
],
out=stringio,
)

assert result == 0

assert (
simple_file.read_text()
== cleandoc(
"""
# Copyright (C) 2015 Mary Sue
# Copyright (C) 2016 Mary Sue
# Copyright (C) 2017 Mary Sue
# Copyright (C) 2018 Mary Sue
# Copyright (C) 2019 Mary Sue
# spdx-FileCopyrightText: 2010 Mary Sue
# spdx-FileCopyrightText: 2011 Mary Sue
# spdx-FileCopyrightText: 2012 Mary Sue
#
# spdx-License-Identifier: GPL-3.0-or-later
pass
"""
).replace("spdx", "SPDX")
)

result = main(
[
"addheader",
"--year",
"2018",
"--license",
"GPL-3.0-or-later",
"--copyright",
"Mary Sue",
"--merge-copyrights",
"foo.py",
],
out=stringio,
)

assert result == 0
assert (
simple_file.read_text()
== cleandoc(
"""
# Copyright (C) 2010 - 2019 Mary Sue
#
# spdx-License-Identifier: GPL-3.0-or-later
pass
"""
).replace("spdx", "SPDX")
)
17 changes: 8 additions & 9 deletions tests/test_project.py
@@ -1,12 +1,11 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Tests for reuse.project."""

# pylint: disable=invalid-name,protected-access

import os
import shutil
from inspect import cleandoc
Expand Down Expand Up @@ -101,7 +100,7 @@ def test_all_files_symlinks(empty_directory):
(empty_directory / "blob.license").write_text(
cleandoc(
"""
spdx-FileCopyrightText: Mary Sue
spdx-FileCopyrightText: Jane Doe
spdx-License-Identifier: GPL-3.0-or-later
"""
Expand Down Expand Up @@ -231,15 +230,15 @@ def test_spdx_info_of_only_copyright(fake_repository):
up on that.
"""
(fake_repository / "foo.py").write_text(
"SPDX-FileCopyrightText: 2017 Mary Sue"
"SPDX-FileCopyrightText: 2017 Jane Doe"
)
project = Project(fake_repository)
spdx_info = project.spdx_info_of("foo.py")
assert not any(spdx_info.spdx_expressions)
assert len(spdx_info.copyright_lines) == 1
assert (
spdx_info.copyright_lines.pop()
== "SPDX-FileCopyrightText: 2017 Mary Sue"
== "SPDX-FileCopyrightText: 2017 Jane Doe"
)


Expand All @@ -255,7 +254,7 @@ def test_spdx_info_of_only_copyright_also_covered_by_debian(fake_repository):
assert any(spdx_info.spdx_expressions)
assert len(spdx_info.copyright_lines) == 2
assert "SPDX-FileCopyrightText: in file" in spdx_info.copyright_lines
assert "2017 Mary Sue" in spdx_info.copyright_lines
assert "2017 Jane Doe" in spdx_info.copyright_lines


def test_spdx_info_of_also_covered_by_dep5(fake_repository):
Expand All @@ -274,7 +273,7 @@ def test_spdx_info_of_also_covered_by_dep5(fake_repository):
assert LicenseSymbol("MIT") in spdx_info.spdx_expressions
assert LicenseSymbol("CC0-1.0") in spdx_info.spdx_expressions
assert "SPDX-FileCopyrightText: in file" in spdx_info.copyright_lines
assert "2017 Mary Sue" in spdx_info.copyright_lines
assert "2017 Jane Doe" in spdx_info.copyright_lines


def test_spdx_info_of_no_duplicates(empty_directory):
Expand Down Expand Up @@ -316,13 +315,13 @@ def test_license_file_detected(empty_directory):
"""
(empty_directory / "foo.py").write_text("foo")
(empty_directory / "foo.py.license").write_text(
"SPDX-FileCopyrightText: 2017 Mary Sue\nSPDX-License-Identifier: MIT\n"
"SPDX-FileCopyrightText: 2017 Jane Doe\nSPDX-License-Identifier: MIT\n"
)

project = Project(empty_directory)
spdx_info = project.spdx_info_of("foo.py")

assert "SPDX-FileCopyrightText: 2017 Mary Sue" in spdx_info.copyright_lines
assert "SPDX-FileCopyrightText: 2017 Jane Doe" in spdx_info.copyright_lines
assert LicenseSymbol("MIT") in spdx_info.spdx_expressions


Expand Down
66 changes: 62 additions & 4 deletions tests/test_report.py
@@ -1,10 +1,10 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Tests for reuse.report"""

# pylint: disable=invalid-name

import os
import sys
Expand Down Expand Up @@ -32,7 +32,7 @@ def test_generate_file_report_file_simple(fake_repository):
project = Project(fake_repository)
result = FileReport.generate(project, "src/source_code.py")
assert result.spdxfile.licenses_in_file == ["GPL-3.0-or-later"]
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Mary Sue"
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Jane Doe"
assert not result.bad_licenses
assert not result.missing_licenses

Expand All @@ -45,7 +45,7 @@ def test_generate_file_report_file_from_different_cwd(fake_repository):
project, fake_repository / "src/source_code.py"
)
assert result.spdxfile.licenses_in_file == ["GPL-3.0-or-later"]
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Mary Sue"
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Jane Doe"
assert not result.bad_licenses
assert not result.missing_licenses

Expand Down Expand Up @@ -76,6 +76,22 @@ def test_generate_file_report_file_bad_license(fake_repository):
assert result.missing_licenses == {"fakelicense"}


def test_generate_file_report_license_contains_plus(fake_repository):
"""Given a license expression akin to Apache-1.0+, LICENSES/Apache-1.0.txt
should be an appropriate license file.
"""
(fake_repository / "foo.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0+"
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()
project = Project(fake_repository)
result = FileReport.generate(project, "foo.py")

assert result.spdxfile.copyright == ""
assert not result.bad_licenses
assert not result.missing_licenses


def test_generate_file_report_exception(fake_repository):
"""Simple generate test to test if the exception is detected."""
project = Project(fake_repository)
Expand All @@ -84,7 +100,7 @@ def test_generate_file_report_exception(fake_repository):
"GPL-3.0-or-later",
"Autoconf-exception-3.0",
}
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Mary Sue"
assert result.spdxfile.copyright == "SPDX-FileCopyrightText: 2017 Jane Doe"
assert not result.bad_licenses
assert not result.missing_licenses

Expand Down Expand Up @@ -153,6 +169,48 @@ def test_generate_project_report_unused_license(
assert result.unused_licenses == {"MIT"}


def test_generate_project_report_unused_license_plus(
fake_repository, multiprocessing
):
"""Apache-1.0+ is not an unused license if LICENSES/Apache-1.0.txt
exists.
Furthermore, Apache-1.0+ is separately identified as a used license.
"""
(fake_repository / "foo.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0+"
)
(fake_repository / "bar.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0"
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()

project = Project(fake_repository)
result = ProjectReport.generate(project, multiprocessing=multiprocessing)

assert not result.unused_licenses
assert {"Apache-1.0", "Apache-1.0+"}.issubset(result.used_licenses)


def test_generate_project_report_unused_license_plus_only_plus(
fake_repository, multiprocessing
):
"""If Apache-1.0+ is the only declared license in the project,
LICENSES/Apache-1.0.txt should not be an unused license.
"""
(fake_repository / "foo.py").write_text(
"SPDX" "-License-Identifier: Apache-1.0+"
)
(fake_repository / "LICENSES/Apache-1.0.txt").touch()

project = Project(fake_repository)
result = ProjectReport.generate(project, multiprocessing=multiprocessing)

assert not result.unused_licenses
assert "Apache-1.0+" in result.used_licenses
assert "Apache-1.0" not in result.used_licenses


def test_generate_project_report_bad_license_in_file(
fake_repository, multiprocessing
):
Expand Down
244 changes: 218 additions & 26 deletions tests/test_util.py
@@ -1,5 +1,8 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2022 Nico Rikken <nico.rikken@fsfe.org>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
# SPDX-FileCopyrightText: 2022 Carmen Bianca Bakker <carmenbianca@fsfe.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later

Expand Down Expand Up @@ -40,14 +43,14 @@ def test_extract_expression():
expressions = ["GPL-3.0+", "GPL-3.0 AND CC0-1.0", "nonsense"]
for expression in expressions:
result = _util.extract_spdx_info(
"SPDX" "-License-Identifier: {}".format(expression)
"SPDX" + f"-License-Identifier: {expression}"
)
assert result.spdx_expressions == {_LICENSING.parse(expression)}


def test_extract_erroneous_expression():
"""Parse an incorrect expression."""
expression = "SPDX" "-License-Identifier: GPL-3.0-or-later AND (MIT OR)"
expression = "SPDX" + "-License-Identifier: GPL-3.0-or-later AND (MIT OR)"
with pytest.raises(ParseError):
_util.extract_spdx_info(expression)

Expand All @@ -62,21 +65,21 @@ def test_extract_no_info():

def test_extract_tab():
"""A tag followed by a tab is also valid."""
result = _util.extract_spdx_info("SPDX" "-License-Identifier:\tMIT")
result = _util.extract_spdx_info("SPDX" + "-License-Identifier:\tMIT")
assert result.spdx_expressions == {_LICENSING.parse("MIT")}


def test_extract_many_whitespace():
"""When a tag is followed by a lot of whitespace, the whitespace should be
filtered out.
"""
result = _util.extract_spdx_info("SPDX" "-License-Identifier: MIT")
result = _util.extract_spdx_info("SPDX" + "-License-Identifier: MIT")
assert result.spdx_expressions == {_LICENSING.parse("MIT")}


def test_extract_bibtex_comment():
"""A special case for BibTex comments."""
expression = "@Comment{SPDX" "-License-Identifier: GPL-3.0-or-later}"
expression = "@Comment{SPDX" + "-License-Identifier: GPL-3.0-or-later}"
result = _util.extract_spdx_info(expression)
assert str(list(result.spdx_expressions)[0]) == "GPL-3.0-or-later"

Expand All @@ -85,32 +88,34 @@ def test_extract_copyright():
"""Given a file with copyright information, have it return that copyright
information.
"""
copyright = "SPDX" "-FileCopyrightText: 2019 Jane Doe"
result = _util.extract_spdx_info(copyright)
assert result.copyright_lines == {copyright}
copyright_line = "SPDX" + "-FileCopyrightText: 2019 Jane Doe"
result = _util.extract_spdx_info(copyright_line)
assert result.copyright_lines == {copyright_line}


def test_extract_copyright_duplicate():
"""When a copyright line is duplicated, only yield one."""
copyright = "SPDX" "-FileCopyrightText: 2019 Jane Doe"
result = _util.extract_spdx_info("\n".join((copyright, copyright)))
assert result.copyright_lines == {copyright}
copyright_line = "SPDX" + "-FileCopyrightText: 2019 Jane Doe"
result = _util.extract_spdx_info(
"\n".join((copyright_line, copyright_line))
)
assert result.copyright_lines == {copyright_line}


def test_extract_copyright_tab():
"""A tag followed by a tab is also valid."""
copyright = "SPDX" "-FileCopyrightText:\t2019 Jane Doe"
result = _util.extract_spdx_info(copyright)
assert result.copyright_lines == {copyright}
copyright_line = "SPDX" + "-FileCopyrightText:\t2019 Jane Doe"
result = _util.extract_spdx_info(copyright_line)
assert result.copyright_lines == {copyright_line}


def test_extract_copyright_many_whitespace():
"""When a tag is followed by a lot of whitespace, that is also valid. The
whitespace is not filtered out.
"""
copyright = "SPDX" "-FileCopyrightText: 2019 Jane Doe"
result = _util.extract_spdx_info(copyright)
assert result.copyright_lines == {copyright}
copyright_line = "SPDX" + "-FileCopyrightText: 2019 Jane Doe"
result = _util.extract_spdx_info(copyright_line)
assert result.copyright_lines == {copyright_line}


def test_extract_copyright_variations():
Expand All @@ -133,26 +138,213 @@ def test_extract_copyright_variations():
assert len(lines) == len(result.copyright_lines)


def test_copyright_from_dep5(copyright):
def test_extract_with_ignore_block():
"""Ensure that the copyright and licensing information inside the ignore
block is actually ignored.
"""
text = cleandoc(
"""
SPDX-FileCopyrightText: 2019 Jane Doe
SPDX-License-Identifier: CC0-1.0
REUSE-IgnoreStart
SPDX-FileCopyrightText: 2019 John Doe
SPDX-License-Identifier: GPL-3.0-or-later
REUSE-IgnoreEnd
SPDX-FileCopyrightText: 2019 Eve
"""
)
result = _util.extract_spdx_info(text)
assert len(result.copyright_lines) == 2
assert len(result.spdx_expressions) == 1


def test_filter_ignore_block_with_comment_style():
"""Test that the ignore block is properly removed if start and end markers
are in comment style.
"""
text = cleandoc(
"""
Relevant text
# REUSE-IgnoreStart
Ignored text
# REUSE-IgnoreEnd
Other relevant text
"""
)
expected = "Relevant text\n# \nOther relevant text"

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_non_comment_style():
"""Test that the ignore block is properly removed if start and end markers
are not comment style.
"""
text = cleandoc(
"""
Relevant text
REUSE-IgnoreStart
Ignored text
REUSE-IgnoreEnd
Other relevant text
"""
)
expected = cleandoc(
"""
Relevant text
Other relevant text
"""
)

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_with_ignored_information_on_same_line():
"""Test that the ignore block is properly removed if there is information to
be ignored on the same line.
"""
text = cleandoc(
"""
Relevant text
REUSE-IgnoreStart Copyright me
Ignored text
sdojfsdREUSE-IgnoreEnd
Other relevant text
"""
)
expected = cleandoc(
"""
Relevant text
Other relevant text
"""
)

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_with_relevant_information_on_same_line():
"""Test that the ignore block is properly removed if it has relevant
information on the same line.
"""
text = cleandoc(
"""
Relevant textREUSE-IgnoreStart
Ignored text
REUSE-IgnoreEndOther relevant text
"""
)
expected = "Relevant textOther relevant text"

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_with_beginning_and_end_on_same_line_correct_order(): # pylint: disable=line-too-long
"""Test that the ignore block is properly removed if it has relevant
information on the same line.
"""
text = cleandoc(
"""
Relevant textREUSE-IgnoreStartIgnored textREUSE-IgnoreEndOther
relevant text
"""
)
expected = cleandoc(
"""
Relevant textOther
relevant text
"""
)

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_with_beginning_and_end_on_same_line_wrong_order():
"""Test that the ignore block is properly removed if it has relevant
information on the same line.
"""
text = "Relevant textREUSE-IgnoreEndOther relevant textREUSE-IgnoreStartIgnored text" # pylint: disable=line-too-long
expected = "Relevant textREUSE-IgnoreEndOther relevant text"

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_without_end():
"""Test that the ignore block is properly removed if it has relevant
information on the same line.
"""
text = cleandoc(
"""
Relevant text
REUSE-IgnoreStart
Ignored text
Other ignored text
"""
)
expected = "Relevant text\n"

result = _util.filter_ignore_block(text)
assert result == expected


def test_filter_ignore_block_with_multiple_ignore_blocks():
"""Test that the ignore block is properly removed if it has relevant
information on the same line.
"""
text = cleandoc(
"""
Relevant text
REUSE-IgnoreStart
Ignored text
REUSE-IgnoreEnd
Other relevant text
REUSE-IgnoreStart
Other ignored text
REUSE-IgnoreEnd
Even more relevant text
"""
)
expected = cleandoc(
"""
Relevant text
Other relevant text
Even more relevant text
"""
)

result = _util.filter_ignore_block(text)
assert result == expected


def test_copyright_from_dep5(dep5_copyright):
"""Verify that the glob in the dep5 file is matched."""
result = _util._copyright_from_dep5("doc/foo.rst", copyright)
result = _util._copyright_from_dep5("doc/foo.rst", dep5_copyright)
assert LicenseSymbol("CC0-1.0") in result.spdx_expressions
assert "2017 Mary Sue" in result.copyright_lines
assert "2017 Jane Doe" in result.copyright_lines


def test_make_copyright_line_simple():
"""Given a simple statement, make it a copyright line."""
assert (
_util.make_copyright_line("hello") == "SPDX"
"-FileCopyrightText: hello"
_util.make_copyright_line("hello")
== "SPDX" + "-FileCopyrightText: hello"
)


def test_make_copyright_line_year():
"""Given a simple statement and a year, make it a copyright line."""
assert (
_util.make_copyright_line("hello", year="2019") == "SPDX"
"-FileCopyrightText: 2019 hello"
_util.make_copyright_line("hello", year="2019")
== "SPDX" + "-FileCopyrightText: 2019 hello"
)


Expand Down Expand Up @@ -212,7 +404,7 @@ def test_make_copyright_line_style_symbol_year():

def test_make_copyright_line_existing_spdx_copyright():
"""Given a copyright line, do nothing."""
value = "SPDX" "-FileCopyrightText: hello"
value = "SPDX" + "-FileCopyrightText: hello"
assert _util.make_copyright_line(value) == value


Expand All @@ -223,7 +415,7 @@ def test_make_copyright_line_existing_other_copyright():


def test_make_copyright_line_multine_error():
"""Given a multiline arguement, expect an error."""
"""Given a multiline argument, expect an error."""
with pytest.raises(RuntimeError):
_util.make_copyright_line("hello\nworld")

Expand Down
2 changes: 1 addition & 1 deletion tests/test_vcs.py
@@ -1,11 +1,11 @@
# SPDX-FileCopyrightText: 2017 Free Software Foundation Europe e.V. <https://fsfe.org>
# SPDX-FileCopyrightText: © 2020 Liferay, Inc. <https://liferay.com>
# SPDX-FileCopyrightText: 2022 Florian Snow <florian@familysnow.net>
#
# SPDX-License-Identifier: GPL-3.0-or-later

"""Tests for reuse.vcs"""

# pylint: disable=invalid-name

import os
from pathlib import Path
Expand Down