diff --git a/.github/workflows/check_codestyle.yml b/.github/workflows/check_codestyle.yml index 15dfd17f9..4cc16b9c1 100644 --- a/.github/workflows/check_codestyle.yml +++ b/.github/workflows/check_codestyle.yml @@ -21,6 +21,12 @@ jobs: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] + exclude: # see https://github.com/actions/runner-images/issues/9770#issuecomment-2085623315 + - python-version: "3.7" + os: macos-latest + include: + - python-version: "3.7" + os: macos-13 steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/install_and_test.yml b/.github/workflows/install_and_test.yml index 4884b9493..0f66cc73f 100644 --- a/.github/workflows/install_and_test.yml +++ b/.github/workflows/install_and_test.yml @@ -18,6 +18,12 @@ jobs: matrix: os: [ ubuntu-latest, macos-latest, windows-latest ] python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ] + exclude: # see https://github.com/actions/runner-images/issues/9770#issuecomment-2085623315 + - python-version: "3.7" + os: macos-latest + include: + - python-version: "3.7" + os: macos-13 steps: - uses: actions/checkout@v3 diff --git a/CHANGELOG.md b/CHANGELOG.md index e55dfaa1d..f17ee022c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## v0.8.3 (2024-09-27) + +### New features and changes + +* tag-value parser: + * fixed license expression error handling + * fixed parsing Tool or Organization as annotator + * allow "NONE" and "NOASSERTION" as strings for fields requiring text +* CLI tool: + * shortened the output of the FileNotFoundError + * now catches decoding errors while parsing +* fixed tag-value output when the ID of a related SPDX element is "NONE" or "NOASSERTION" +* spdx3: + * added REQUIREMENT type to software_purpose + * unindent creation information in element_writer +* internal: + * replaced remaining occurrences of Licensing() with spdx_licensing + * fixed CI not working for Python 3.7 on latest MacOS + +### Contributors + +This release was made possible by the following contributors. Thank you very much! + +* Stanislav Pankevich @stanislaw +* Meret Behrens @meretp +* Maximilian Huber @maxhbr +* Armin Tänzer @armintaenzertng + ## v0.8.2 (2023-10-12) ### New features and changes diff --git a/src/spdx_tools/spdx/clitools/pyspdxtools.py b/src/spdx_tools/spdx/clitools/pyspdxtools.py index 8603b8401..4219c6b81 100644 --- a/src/spdx_tools/spdx/clitools/pyspdxtools.py +++ b/src/spdx_tools/spdx/clitools/pyspdxtools.py @@ -14,9 +14,13 @@ # limitations under the License. import logging import sys +from json import JSONDecodeError +from xml.parsers.expat import ExpatError +from xml.sax import SAXParseException import click from beartype.typing import List +from yaml.scanner import ScannerError from spdx_tools.spdx.graph_generation import export_graph_from_document from spdx_tools.spdx.model import Document @@ -113,6 +117,26 @@ def main(infile: str, outfile: str, version: str, novalidation: bool, graph: boo logging.error(log_string) sys.exit(1) + except JSONDecodeError as err: + logging.error(f"Invalid JSON provided: {err.args[0]}") + sys.exit(1) + + except ScannerError as err: + logging.error("Invalid YAML provided: " + "\n".join([str(arg) for arg in err.args])) + sys.exit(1) + + except ExpatError as err: + logging.error(f"Invalid XML provided: {err.args[0]}") + sys.exit(1) + + except SAXParseException as err: + logging.error(f"Invalid RDF-XML provided: {str(err)}") + sys.exit(1) + + except FileNotFoundError as err: + logging.error(f"{err.strerror}: {err.filename}") + sys.exit(1) + if __name__ == "__main__": main() diff --git a/src/spdx_tools/spdx/parser/jsonlikedict/license_expression_parser.py b/src/spdx_tools/spdx/parser/jsonlikedict/license_expression_parser.py index 29c0ebb99..5a3b545f6 100644 --- a/src/spdx_tools/spdx/parser/jsonlikedict/license_expression_parser.py +++ b/src/spdx_tools/spdx/parser/jsonlikedict/license_expression_parser.py @@ -2,8 +2,9 @@ # # SPDX-License-Identifier: Apache-2.0 from beartype.typing import Union -from license_expression import ExpressionError, LicenseExpression, Licensing +from license_expression import ExpressionError, LicenseExpression +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.model import SpdxNoAssertion, SpdxNone from spdx_tools.spdx.parser.error import SPDXParsingError @@ -18,7 +19,7 @@ def parse_license_expression(license_expression_str: str) -> Union[LicenseExpres return SpdxNone() try: - license_expression = Licensing().parse(license_expression_str) + license_expression = spdx_licensing.parse(license_expression_str) except ExpressionError as err: err_msg = f'Error parsing LicenseExpression: "{license_expression_str}"' if err.args: diff --git a/src/spdx_tools/spdx/parser/tagvalue/parser.py b/src/spdx_tools/spdx/parser/tagvalue/parser.py index a11f08679..50096bda2 100644 --- a/src/spdx_tools/spdx/parser/tagvalue/parser.py +++ b/src/spdx_tools/spdx/parser/tagvalue/parser.py @@ -14,7 +14,7 @@ import re from beartype.typing import Any, Dict, List -from license_expression import get_spdx_licensing +from license_expression import ExpressionError, get_spdx_licensing from ply import yacc from ply.yacc import LRParser @@ -212,11 +212,14 @@ def p_generic_value(self, p): def p_unknown_tag(self, p): self.logger.append(f"Unknown tag provided in line {p.lineno(1)}") - @grammar_rule("text_or_line : TEXT") + @grammar_rule("text_or_line : TEXT\n line_or_no_assertion_or_none : TEXT") def p_text(self, p): p[0] = str_from_text(p[1]) - @grammar_rule("text_or_line : LINE\n line_or_no_assertion : LINE\nline_or_no_assertion_or_none : text_or_line") + @grammar_rule( + "text_or_line : LINE\n line_or_no_assertion : LINE\nline_or_no_assertion_or_none : LINE\n" + "text_or_line : NO_ASSERTION\n text_or_line : NONE" + ) def p_line(self, p): p[0] = p[1] @@ -233,7 +236,13 @@ def p_none(self, p): @grammar_rule("license_or_no_assertion_or_none : LINE") def p_license(self, p): - p[0] = get_spdx_licensing().parse(p[1]) + try: + p[0] = get_spdx_licensing().parse(p[1]) + except ExpressionError as err: + error_message = f"Error while parsing license expression: {p[1]}" + if err.args: + error_message += f": {err.args[0]}" + self.current_element["logger"].append(error_message) @grammar_rule("actor_or_no_assertion : PERSON_VALUE\n | ORGANIZATION_VALUE") def p_actor_values(self, p): @@ -482,7 +491,7 @@ def p_snippet_range(self, p): # parsing methods for annotation - @grammar_rule("annotator : ANNOTATOR PERSON_VALUE\n| TOOL_VALUE\n| ORGANIZATION_VALUE") + @grammar_rule("annotator : ANNOTATOR PERSON_VALUE\n| ANNOTATOR TOOL_VALUE\n| ANNOTATOR ORGANIZATION_VALUE") def p_annotator(self, p): self.initialize_new_current_element(Annotation) set_value(p, self.current_element, method_to_apply=ActorParser.parse_actor) diff --git a/src/spdx_tools/spdx/writer/tagvalue/tagvalue_writer_helper_functions.py b/src/spdx_tools/spdx/writer/tagvalue/tagvalue_writer_helper_functions.py index 907c155b7..98f670252 100644 --- a/src/spdx_tools/spdx/writer/tagvalue/tagvalue_writer_helper_functions.py +++ b/src/spdx_tools/spdx/writer/tagvalue/tagvalue_writer_helper_functions.py @@ -83,7 +83,9 @@ def scan_relationships( files_by_spdx_id = {file.spdx_id: file for file in files} packages_spdx_ids = [package.spdx_id for package in packages] for relationship in relationships: - if ( + if relationship.related_spdx_element_id in [SpdxNoAssertion(), SpdxNone()]: + relationships_to_write.append(relationship) + elif ( relationship.relationship_type == RelationshipType.CONTAINS and relationship.spdx_element_id in packages_spdx_ids and relationship.related_spdx_element_id in files_by_spdx_id.keys() diff --git a/src/spdx_tools/spdx3/model/software/software_purpose.py b/src/spdx_tools/spdx3/model/software/software_purpose.py index 4e071181f..e3a4d48cf 100644 --- a/src/spdx_tools/spdx3/model/software/software_purpose.py +++ b/src/spdx_tools/spdx3/model/software/software_purpose.py @@ -24,5 +24,6 @@ class SoftwarePurpose(Enum): OPERATING_SYSTEM = auto() OTHER = auto() PATCH = auto() + REQUIREMENT = auto() SOURCE = auto() TEST = auto() diff --git a/src/spdx_tools/spdx3/writer/console/console.py b/src/spdx_tools/spdx3/writer/console/console.py index a2ec81ed5..28b5f9cfa 100644 --- a/src/spdx_tools/spdx3/writer/console/console.py +++ b/src/spdx_tools/spdx3/writer/console/console.py @@ -22,6 +22,6 @@ def write_value(tag: str, value: Optional[Union[bool, str, dict, list, Enum]], o def write_and_possibly_indent(text: str, indent: bool, out: TextIO): if indent: - out.write(f"\t{text}\n") + out.write(f" {text}\n") else: out.write(f"{text}\n") diff --git a/tests/spdx/jsonschema/test_file_converter.py b/tests/spdx/jsonschema/test_file_converter.py index 8e6c14049..560ecb1a4 100644 --- a/tests/spdx/jsonschema/test_file_converter.py +++ b/tests/spdx/jsonschema/test_file_converter.py @@ -7,8 +7,8 @@ from unittest.mock import MagicMock, NonCallableMagicMock import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.jsonschema.annotation_converter import AnnotationConverter from spdx_tools.spdx.jsonschema.file_converter import FileConverter from spdx_tools.spdx.jsonschema.file_properties import FileProperty @@ -81,8 +81,8 @@ def test_successful_conversion(converter: FileConverter): spdx_id="spdxId", checksums=[Checksum(ChecksumAlgorithm.SHA224, "sha224"), Checksum(ChecksumAlgorithm.MD2, "md2")], file_types=[FileType.SPDX, FileType.OTHER], - license_concluded=Licensing().parse("MIT and GPL-2.0"), - license_info_in_file=[Licensing().parse("MIT"), Licensing().parse("GPL-2.0"), SpdxNoAssertion()], + license_concluded=spdx_licensing.parse("MIT and GPL-2.0"), + license_info_in_file=[spdx_licensing.parse("MIT"), spdx_licensing.parse("GPL-2.0"), SpdxNoAssertion()], license_comment="licenseComment", copyright_text="copyrightText", comment="comment", @@ -115,8 +115,8 @@ def test_successful_conversion(converter: FileConverter): converter.json_property_name(FileProperty.FILE_NAME): "name", converter.json_property_name(FileProperty.FILE_TYPES): ["SPDX", "OTHER"], converter.json_property_name(FileProperty.LICENSE_COMMENTS): "licenseComment", - converter.json_property_name(FileProperty.LICENSE_CONCLUDED): "MIT AND GPL-2.0", - converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES): ["MIT", "GPL-2.0", "NOASSERTION"], + converter.json_property_name(FileProperty.LICENSE_CONCLUDED): "MIT AND GPL-2.0-only", + converter.json_property_name(FileProperty.LICENSE_INFO_IN_FILES): ["MIT", "GPL-2.0-only", "NOASSERTION"], converter.json_property_name(FileProperty.NOTICE_TEXT): "notice", } diff --git a/tests/spdx/jsonschema/test_package_converter.py b/tests/spdx/jsonschema/test_package_converter.py index 9365c8214..c6e63d9f8 100644 --- a/tests/spdx/jsonschema/test_package_converter.py +++ b/tests/spdx/jsonschema/test_package_converter.py @@ -7,8 +7,8 @@ from unittest.mock import MagicMock, NonCallableMagicMock import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.jsonschema.annotation_converter import AnnotationConverter from spdx_tools.spdx.jsonschema.package_converter import PackageConverter from spdx_tools.spdx.jsonschema.package_properties import PackageProperty @@ -123,9 +123,9 @@ def test_successful_conversion(converter: PackageConverter): checksums=[Checksum(ChecksumAlgorithm.SHA1, "sha1"), Checksum(ChecksumAlgorithm.BLAKE2B_256, "blake")], homepage="homepage", source_info="sourceInfo", - license_concluded=Licensing().parse("MIT and GPL-2.0"), - license_info_from_files=[Licensing().parse("MIT"), Licensing().parse("GPL-2.0")], - license_declared=Licensing().parse("MIT or GPL-2.0 "), + license_concluded=spdx_licensing.parse("MIT and GPL-2.0"), + license_info_from_files=[spdx_licensing.parse("MIT"), spdx_licensing.parse("GPL-2.0")], + license_declared=spdx_licensing.parse("MIT or GPL-2.0 "), license_comment="licenseComment", copyright_text="copyrightText", summary="summary", @@ -168,9 +168,9 @@ def test_successful_conversion(converter: PackageConverter): ], converter.json_property_name(PackageProperty.HOMEPAGE): "homepage", converter.json_property_name(PackageProperty.SOURCE_INFO): "sourceInfo", - converter.json_property_name(PackageProperty.LICENSE_CONCLUDED): "MIT AND GPL-2.0", - converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES): ["MIT", "GPL-2.0"], - converter.json_property_name(PackageProperty.LICENSE_DECLARED): "MIT OR GPL-2.0", + converter.json_property_name(PackageProperty.LICENSE_CONCLUDED): "MIT AND GPL-2.0-only", + converter.json_property_name(PackageProperty.LICENSE_INFO_FROM_FILES): ["MIT", "GPL-2.0-only"], + converter.json_property_name(PackageProperty.LICENSE_DECLARED): "MIT OR GPL-2.0-only", converter.json_property_name(PackageProperty.LICENSE_COMMENTS): "licenseComment", converter.json_property_name(PackageProperty.COPYRIGHT_TEXT): "copyrightText", converter.json_property_name(PackageProperty.SUMMARY): "summary", diff --git a/tests/spdx/jsonschema/test_snippet_converter.py b/tests/spdx/jsonschema/test_snippet_converter.py index a677343b0..36eecd4ac 100644 --- a/tests/spdx/jsonschema/test_snippet_converter.py +++ b/tests/spdx/jsonschema/test_snippet_converter.py @@ -7,8 +7,8 @@ from unittest.mock import MagicMock, NonCallableMagicMock import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.jsonschema.annotation_converter import AnnotationConverter from spdx_tools.spdx.jsonschema.snippet_converter import SnippetConverter from spdx_tools.spdx.jsonschema.snippet_properties import SnippetProperty @@ -72,8 +72,8 @@ def test_successful_conversion(converter: SnippetConverter): file_spdx_id=file_spdx_id, byte_range=(1, 2), line_range=(3, 4), - license_concluded=Licensing().parse("MIT and GPL-2.0"), - license_info_in_snippet=[Licensing().parse("MIT"), Licensing().parse("GPL-2.0")], + license_concluded=spdx_licensing.parse("MIT and GPL-2.0"), + license_info_in_snippet=[spdx_licensing.parse("MIT"), spdx_licensing.parse("GPL-2.0")], license_comment="licenseComment", copyright_text="copyrightText", comment="comment", @@ -98,8 +98,8 @@ def test_successful_conversion(converter: SnippetConverter): converter.json_property_name(SnippetProperty.COMMENT): "comment", converter.json_property_name(SnippetProperty.COPYRIGHT_TEXT): "copyrightText", converter.json_property_name(SnippetProperty.LICENSE_COMMENTS): "licenseComment", - converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED): "MIT AND GPL-2.0", - converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS): ["MIT", "GPL-2.0"], + converter.json_property_name(SnippetProperty.LICENSE_CONCLUDED): "MIT AND GPL-2.0-only", + converter.json_property_name(SnippetProperty.LICENSE_INFO_IN_SNIPPETS): ["MIT", "GPL-2.0-only"], converter.json_property_name(SnippetProperty.NAME): "name", converter.json_property_name(SnippetProperty.RANGES): [ { diff --git a/tests/spdx/model/test_package.py b/tests/spdx/model/test_package.py index c533b2812..2f015f1e0 100644 --- a/tests/spdx/model/test_package.py +++ b/tests/spdx/model/test_package.py @@ -6,8 +6,9 @@ from unittest import mock import pytest -from license_expression import LicenseExpression, Licensing +from license_expression import LicenseExpression +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.model import Checksum, ChecksumAlgorithm, Package, PackagePurpose, SpdxNoAssertion, SpdxNone @@ -30,7 +31,7 @@ def test_correct_initialization(actor, verif_code, checksum, ext_ref): "homepage", "source_info", None, - [Licensing().parse("license and expression"), SpdxNoAssertion()], + [spdx_licensing.parse("license and expression"), SpdxNoAssertion()], SpdxNone(), "comment on license", "copyright", @@ -57,7 +58,7 @@ def test_correct_initialization(actor, verif_code, checksum, ext_ref): assert package.homepage == "homepage" assert package.source_info == "source_info" assert package.license_concluded is None - assert package.license_info_from_files == [Licensing().parse("license and expression"), SpdxNoAssertion()] + assert package.license_info_from_files == [spdx_licensing.parse("license and expression"), SpdxNoAssertion()] assert package.license_declared == SpdxNone() assert package.license_comment == "comment on license" assert package.copyright_text == "copyright" diff --git a/tests/spdx/parser/jsonlikedict/test_file_parser.py b/tests/spdx/parser/jsonlikedict/test_file_parser.py index e1c6b7a5e..bbe5b70ca 100644 --- a/tests/spdx/parser/jsonlikedict/test_file_parser.py +++ b/tests/spdx/parser/jsonlikedict/test_file_parser.py @@ -4,8 +4,8 @@ from unittest import TestCase import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.model import Checksum, ChecksumAlgorithm, FileType, SpdxNoAssertion, SpdxNone from spdx_tools.spdx.parser.error import SPDXParsingError from spdx_tools.spdx.parser.jsonlikedict.dict_parsing_functions import parse_list_of_elements @@ -82,10 +82,10 @@ def test_parse_file(copyright_text, expected_copyright_text): "IBM Corporation", ], ) - assert file.license_concluded == Licensing().parse("(LGPL-2.0-only OR LicenseRef-2)") + assert file.license_concluded == spdx_licensing.parse("(LGPL-2.0-only OR LicenseRef-2)") TestCase().assertCountEqual( file.license_info_in_file, - [Licensing().parse("GPL-2.0-only"), Licensing().parse("LicenseRef-2"), SpdxNoAssertion()], + [spdx_licensing.parse("GPL-2.0-only"), spdx_licensing.parse("LicenseRef-2"), SpdxNoAssertion()], ) assert ( file.license_comment == "The concluded license was taken from the package level that the file was included in." diff --git a/tests/spdx/parser/jsonlikedict/test_license_expression_parser.py b/tests/spdx/parser/jsonlikedict/test_license_expression_parser.py index fca36b837..d78a88af3 100644 --- a/tests/spdx/parser/jsonlikedict/test_license_expression_parser.py +++ b/tests/spdx/parser/jsonlikedict/test_license_expression_parser.py @@ -16,6 +16,8 @@ [ ("First License", spdx_licensing.parse("First License")), ("Second License", spdx_licensing.parse("Second License")), + ("Apache-1.1", spdx_licensing.parse("Apache-1.1")), + ("Zlib", spdx_licensing.parse("zlib")), ("NOASSERTION", SpdxNoAssertion()), ("NONE", SpdxNone()), ], @@ -34,7 +36,8 @@ def test_parse_license_expression(license_expression_str, expected_license): ( "LGPL-2.1, GPL-2.0, GPL-3.0", [ - "Error parsing LicenseExpression: \"LGPL-2.1, GPL-2.0, GPL-3.0\": Invalid license key: the valid characters are: letters and numbers, underscore, dot, colon or hyphen signs and spaces: 'LGPL-2.1, GPL-2.0, GPL-3.0'" # noqa: E501 + # the error message we receive from the license_expression library somehow cuts off the last license + "Error parsing LicenseExpression: \"LGPL-2.1, GPL-2.0, GPL-3.0\": Invalid license key: the valid characters are: letters and numbers, underscore, dot, colon or hyphen signs and spaces: 'LGPL-2.1, GPL-2.0,'" # noqa: E501 ], ), ("Apache License (2.0)", ['Error parsing LicenseExpression: "Apache License (2.0)"']), diff --git a/tests/spdx/parser/jsonlikedict/test_package_parser.py b/tests/spdx/parser/jsonlikedict/test_package_parser.py index 5b933df8b..209cf5c0c 100644 --- a/tests/spdx/parser/jsonlikedict/test_package_parser.py +++ b/tests/spdx/parser/jsonlikedict/test_package_parser.py @@ -5,8 +5,8 @@ from unittest import TestCase import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.model import ( Actor, ActorType, @@ -173,17 +173,17 @@ def test_parse_package( ) assert package.homepage == expected_homepage assert package.source_info == "uses glibc-2_11-branch from git://sourceware.org/git/glibc.git." - assert package.license_concluded == Licensing().parse("(LGPL-2.0-only OR LicenseRef-3)") + assert package.license_concluded == spdx_licensing.parse("(LGPL-2.0-only OR LicenseRef-3)") TestCase().assertCountEqual( package.license_info_from_files, [ - Licensing().parse("GPL-2.0-only"), - Licensing().parse("LicenseRef-2"), - Licensing().parse("LicenseRef-1"), + spdx_licensing.parse("GPL-2.0-only"), + spdx_licensing.parse("LicenseRef-2"), + spdx_licensing.parse("LicenseRef-1"), SpdxNoAssertion(), ], ) - assert package.license_declared == Licensing().parse("(LGPL-2.0-only AND LicenseRef-3)") + assert package.license_declared == spdx_licensing.parse("(LGPL-2.0-only AND LicenseRef-3)") assert ( package.license_comment == "The license for this project changed with the release of version x.y. The version of the project included" diff --git a/tests/spdx/parser/jsonlikedict/test_snippet_parser.py b/tests/spdx/parser/jsonlikedict/test_snippet_parser.py index 257a00e3a..3b9b62a19 100644 --- a/tests/spdx/parser/jsonlikedict/test_snippet_parser.py +++ b/tests/spdx/parser/jsonlikedict/test_snippet_parser.py @@ -4,8 +4,8 @@ from unittest import TestCase import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.model import SpdxNoAssertion, SpdxNone from spdx_tools.spdx.parser.error import SPDXParsingError from spdx_tools.spdx.parser.jsonlikedict.snippet_parser import SnippetParser @@ -65,8 +65,8 @@ def test_parse_snippet(copyright_text, expected_copyright_text): assert snippet.byte_range == (310, 420) assert snippet.line_range == (5, 23) assert snippet.file_spdx_id == "SPDXRef-DoapSource" - assert snippet.license_info_in_snippet == [Licensing().parse("GPL-2.0-only"), SpdxNoAssertion()] - assert snippet.license_concluded == Licensing().parse("GPL-2.0-only") + assert snippet.license_info_in_snippet == [spdx_licensing.parse("GPL-2.0-only"), SpdxNoAssertion()] + assert snippet.license_concluded == spdx_licensing.parse("GPL-2.0-only") assert snippet.attribution_texts == ["Some example attibution text."] diff --git a/tests/spdx/parser/tagvalue/test_annotation_parser.py b/tests/spdx/parser/tagvalue/test_annotation_parser.py index 629fe72e9..f10ebe8c6 100644 --- a/tests/spdx/parser/tagvalue/test_annotation_parser.py +++ b/tests/spdx/parser/tagvalue/test_annotation_parser.py @@ -34,6 +34,28 @@ def test_parse_annotation(): assert annotation.spdx_id == DOCUMENT_SPDX_ID +def test_parse_annotation_with_organization_as_annotator(): + parser = Parser() + annotation_str = "\n".join( + [ + "Annotator: Organization: some-organization", + "AnnotationDate: 2010-01-29T18:30:22Z", + "AnnotationComment: Document level annotation", + "AnnotationType: OTHER", + f"SPDXREF: {DOCUMENT_SPDX_ID}", + ] + ) + document = parser.parse("\n".join([DOCUMENT_STR, annotation_str])) + assert document is not None + assert len(document.annotations) == 1 + annotation = document.annotations[0] + assert annotation.annotator.name == "some-organization" + assert annotation.annotation_date == datetime(2010, 1, 29, 18, 30, 22) + assert annotation.annotation_comment == "Document level annotation" + assert annotation.annotation_type == AnnotationType.OTHER + assert annotation.spdx_id == DOCUMENT_SPDX_ID + + @pytest.mark.parametrize( "annotation_str, expected_message", [ diff --git a/tests/spdx/parser/tagvalue/test_tag_value_parser.py b/tests/spdx/parser/tagvalue/test_tag_value_parser.py index 33defcb9d..c866e2726 100644 --- a/tests/spdx/parser/tagvalue/test_tag_value_parser.py +++ b/tests/spdx/parser/tagvalue/test_tag_value_parser.py @@ -6,7 +6,7 @@ import pytest from spdx_tools.spdx.constants import DOCUMENT_SPDX_ID -from spdx_tools.spdx.model import Relationship, RelationshipType +from spdx_tools.spdx.model import Relationship, RelationshipType, SpdxNoAssertion, SpdxNone from spdx_tools.spdx.parser.error import SPDXParsingError from spdx_tools.spdx.parser.tagvalue.parser import Parser from tests.spdx.parser.tagvalue.test_creation_info_parser import DOCUMENT_STR @@ -98,3 +98,67 @@ def test_document_with_mixed_values(): "Element Package is not the current element in scope, probably the expected " "tag to start the element (PackageName) is missing. Line: 4" ] + + +def test_faulty_license_expression(): + parser = Parser() + document_str = "\n".join( + [ + f"SPDXID:{DOCUMENT_SPDX_ID}", + "FileName: File with faulty license expression", + "SPDXID: SPDXRef-File", + "FileChecksum: SHA1: d6a770ba38583ed4bb4525bd96e50461655d2759", + "LicenseConcluded: LicenseRef-foo/bar", + "PackageName: Package with faulty license expression", + "SPDXID: SPDXRef-Package", + "PackageDownloadLocation: www.download.com", + "PackageLicenseConcluded: LicenseRef-bar/foo", + "SnippetSPDXID: SPDXRef-Snippet", + "SnippetName: Snippet with faulty license expression", + "SnippetLicenseConcluded: LicenseRef-foo/foo", + ] + ) + + with pytest.raises(SPDXParsingError) as err: + parser.parse(document_str) + + assert err.value.get_messages() == [ + 'Error while parsing File: ["Error while parsing license expression: ' + "LicenseRef-foo/bar: Invalid license key: the valid characters are: letters " + "and numbers, underscore, dot, colon or hyphen signs and spaces: " + "'LicenseRef-foo/bar'\"]", + 'Error while parsing Package: ["Error while parsing license expression: ' + "LicenseRef-bar/foo: Invalid license key: the valid characters are: letters " + "and numbers, underscore, dot, colon or hyphen signs and spaces: " + "'LicenseRef-bar/foo'\"]", + 'Error while parsing Snippet: ["Error while parsing license expression: ' + "LicenseRef-foo/foo: Invalid license key: the valid characters are: letters " + "and numbers, underscore, dot, colon or hyphen signs and spaces: " + "'LicenseRef-foo/foo'\"]", + ] + + +def test_parse_none_or_no_assertion_as_text(): + parser = Parser() + document_str = "\n".join( + [ + DOCUMENT_STR, + "PackageName: Test", + "SPDXID: SPDXRef-Package", + "PackageDownloadLocation: http://example.com/test", + "FilesAnalyzed: true", + "PackageSummary: NONE", + "PackageSourceInfo: NOASSERTION", + "PackageLicenseConcluded: NONE", + "PackageLicenseDeclared: NOASSERTION", + ] + ) + document = parser.parse(document_str) + assert document is not None + package = document.packages[0] + assert package.name == "Test" + assert package.spdx_id == "SPDXRef-Package" + assert package.source_info == "NOASSERTION" + assert package.summary == "NONE" + assert package.license_concluded == SpdxNone() + assert package.license_declared == SpdxNoAssertion() diff --git a/tests/spdx/test_cli.py b/tests/spdx/test_cli.py index 6fcbe3733..0019cf3a3 100644 --- a/tests/spdx/test_cli.py +++ b/tests/spdx/test_cli.py @@ -42,6 +42,7 @@ def test_cli_with_system_exit_code_0(options): "data/invalid/spdx-trivy-vmware_log-intelligence-fluentd-sha256_086af034f561f343f633be9d9f9e95f65ae6c61b8ddb2c6755ef5bb25b40f53a.json", # noqa: E501 ), ), + ("-i", "non_existent_file.spdx"), ], ) def test_cli_with_system_exit_code_1(options): diff --git a/tests/spdx/validation/test_package_validator.py b/tests/spdx/validation/test_package_validator.py index a6ef976ef..d0516fff2 100644 --- a/tests/spdx/validation/test_package_validator.py +++ b/tests/spdx/validation/test_package_validator.py @@ -6,8 +6,8 @@ from unittest import TestCase import pytest -from license_expression import Licensing +from spdx_tools.common.spdx_licensing import spdx_licensing from spdx_tools.spdx.constants import DOCUMENT_SPDX_ID from spdx_tools.spdx.model import Relationship, RelationshipType, SpdxNoAssertion, SpdxNone from spdx_tools.spdx.validation.package_validator import validate_package, validate_package_within_document @@ -45,7 +45,7 @@ def test_valid_package(): ( package_fixture( files_analyzed=False, - license_info_from_files=[Licensing().parse("some_license")], + license_info_from_files=[spdx_licensing.parse("some_license")], verification_code=None, ), "license_info_from_files must be None if files_analyzed is False, but is: [LicenseSymbol('some_license', " diff --git a/tests/spdx/writer/tagvalue/test_tagvalue_writer_helper_functions.py b/tests/spdx/writer/tagvalue/test_tagvalue_writer_helper_functions.py index d101fd37c..4949b434b 100644 --- a/tests/spdx/writer/tagvalue/test_tagvalue_writer_helper_functions.py +++ b/tests/spdx/writer/tagvalue/test_tagvalue_writer_helper_functions.py @@ -5,7 +5,7 @@ import pytest -from spdx_tools.spdx.model import ActorType, RelationshipType, SpdxNoAssertion +from spdx_tools.spdx.model import ActorType, RelationshipType, SpdxNoAssertion, SpdxNone from spdx_tools.spdx.writer.tagvalue.tagvalue_writer_helper_functions import scan_relationships, write_actor from tests.spdx.fixtures import actor_fixture, file_fixture, package_fixture, relationship_fixture @@ -16,6 +16,18 @@ def test_scan_relationships(): packages = [package_fixture(spdx_id=first_package_spdx_id), package_fixture(spdx_id=second_package_spdx_id)] file_spdx_id = "SPDXRef-File" files = [file_fixture(spdx_id=file_spdx_id)] + no_assertion_relationship = relationship_fixture( + spdx_element_id=second_package_spdx_id, + relationship_type=RelationshipType.CONTAINS, + related_spdx_element_id=SpdxNoAssertion(), + comment=None, + ) + none_relationship = relationship_fixture( + spdx_element_id=second_package_spdx_id, + relationship_type=RelationshipType.CONTAINS, + related_spdx_element_id=SpdxNone(), + comment=None, + ) relationships = [ relationship_fixture( spdx_element_id=first_package_spdx_id, @@ -29,11 +41,13 @@ def test_scan_relationships(): related_spdx_element_id=file_spdx_id, comment=None, ), + no_assertion_relationship, + none_relationship, ] relationships_to_write, contained_files_by_package_id = scan_relationships(relationships, packages, files) - assert relationships_to_write == [] + assert relationships_to_write == [no_assertion_relationship, none_relationship] assert contained_files_by_package_id == {first_package_spdx_id: files, second_package_spdx_id: files} diff --git a/tests/spdx3/writer/tag_value/test_write_document.py b/tests/spdx3/writer/tag_value/test_write_document.py new file mode 100644 index 000000000..580c61b81 --- /dev/null +++ b/tests/spdx3/writer/tag_value/test_write_document.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: 2024 spdx contributors +# +# SPDX-License-Identifier: Apache-2.0 +import io +from datetime import datetime + +from semantic_version import Version + +from spdx_tools.spdx3.model import CreationInfo, ProfileIdentifierType, SpdxDocument +from spdx_tools.spdx3.writer.console.spdx_document_writer import write_spdx_document + + +def test_render_creation_info(): + fake_datetime = datetime(year=2024, month=1, day=1) + spec_version = Version("3.0.0") + creation_info = CreationInfo( + spec_version=spec_version, + created=fake_datetime, + created_by=[], + profile=[ProfileIdentifierType.SOFTWARE], + ) + spdx_document = SpdxDocument( + spdx_id="SPDXRef-FOO", + name="BAR", + element=[], + root_element=[], + creation_info=creation_info, + ) + output_str = io.StringIO() + write_spdx_document(spdx_document, text_output=output_str) + + assert ( + output_str.getvalue() + == """\ +## SPDX Document +SPDXID: SPDXRef-FOO +name: BAR +# Creation Information + specVersion: 3.0.0 + created: 2024-01-01T00:00:00Z + profile: SOFTWARE + data license: CC0-1.0 +elements: +""" # noqa: W291 # elements: are printed with a space + )