Skip to content

Commit 55c6386

Browse files
[issue-693] TV writer: fix for same file in multiple packages
Signed-off-by: Armin Tänzer <armin.taenzer@tngtech.com>
1 parent ce8cf68 commit 55c6386

2 files changed

Lines changed: 114 additions & 7 deletions

File tree

src/spdx_tools/spdx/writer/tagvalue/tagvalue_writer.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from beartype.typing import List, TextIO
1212

1313
from spdx_tools.spdx.document_utils import create_document_without_duplicates
14-
from spdx_tools.spdx.model import Document
14+
from spdx_tools.spdx.model import Document, Relationship, RelationshipType
1515
from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document
1616
from spdx_tools.spdx.validation.validation_message import ValidationMessage
1717
from spdx_tools.spdx.writer.tagvalue.annotation_writer import write_annotation
@@ -70,17 +70,27 @@ def write_document(document: Document, text_output: TextIO):
7070
file_ids_with_contained_snippets[file.spdx_id], write_snippet, text_output, with_separator=True
7171
)
7272

73+
already_written_file_ids = [] # a file can belong to multiple packages but must appear only once
7374
for package in document.packages:
7475
write_package(package, text_output)
7576
write_separator(text_output)
7677
if package.spdx_id in contained_files_by_package_id:
7778
for file in contained_files_by_package_id[package.spdx_id]:
78-
write_file(file, text_output)
79-
write_separator(text_output)
80-
if file.spdx_id in file_ids_with_contained_snippets:
81-
write_list_of_elements(
82-
file_ids_with_contained_snippets[file.spdx_id], write_snippet, text_output, with_separator=True
79+
if file.spdx_id in already_written_file_ids:
80+
relationships_to_write.append(
81+
Relationship(package.spdx_id, RelationshipType.CONTAINS, file.spdx_id)
8382
)
83+
else:
84+
write_file(file, text_output)
85+
write_separator(text_output)
86+
if file.spdx_id in file_ids_with_contained_snippets:
87+
write_list_of_elements(
88+
file_ids_with_contained_snippets[file.spdx_id],
89+
write_snippet,
90+
text_output,
91+
with_separator=True,
92+
)
93+
already_written_file_ids.append(file.spdx_id)
8494

8595
write_optional_heading(document.extracted_licensing_info, "## License Information\n", text_output)
8696
write_list_of_elements(

tests/spdx/writer/tagvalue/test_tagvalue_writer.py

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,24 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
import os
6+
from datetime import datetime
67
from unittest.mock import MagicMock, call, mock_open, patch
78

89
import pytest
910

10-
from spdx_tools.spdx.model import File, Package, Relationship, RelationshipType, Snippet
11+
from spdx_tools.spdx.model import (
12+
Actor,
13+
ActorType,
14+
Checksum,
15+
ChecksumAlgorithm,
16+
CreationInfo,
17+
Document,
18+
File,
19+
Package,
20+
Relationship,
21+
RelationshipType,
22+
Snippet,
23+
)
1124
from spdx_tools.spdx.parser.tagvalue import tagvalue_parser
1225
from spdx_tools.spdx.writer.tagvalue.tagvalue_writer import write_document, write_document_to_file
1326
from tests.spdx.fixtures import checksum_fixture, document_fixture
@@ -122,3 +135,87 @@ def test_correct_order_of_elements():
122135
call("\n"),
123136
]
124137
)
138+
139+
140+
def test_same_file_in_multiple_packages():
141+
creation_info = CreationInfo(
142+
spdx_version="SPDX-2.3",
143+
spdx_id="SPDXRef-DOCUMENT",
144+
data_license="CC0-1.0",
145+
name="SPDX Lite Document",
146+
document_namespace="https://test.namespace.com",
147+
creators=[Actor(ActorType.PERSON, "John Doe")],
148+
created=datetime(2023, 3, 14, 8, 49),
149+
)
150+
package_a = Package(
151+
name="Example package A",
152+
spdx_id="SPDXRef-Package-A",
153+
download_location="https://download.com",
154+
)
155+
package_b = Package(
156+
name="Example package B",
157+
spdx_id="SPDXRef-Package-B",
158+
download_location="https://download.com",
159+
)
160+
file = File(
161+
name="Example file",
162+
spdx_id="SPDXRef-File",
163+
checksums=[Checksum(ChecksumAlgorithm.SHA1, "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12")],
164+
)
165+
166+
relationships = [
167+
Relationship("SPDXRef-DOCUMENT", RelationshipType.DESCRIBES, "SPDXRef-Package-A"),
168+
Relationship("SPDXRef-DOCUMENT", RelationshipType.DESCRIBES, "SPDXRef-Package-B"),
169+
Relationship("SPDXRef-Package-A", RelationshipType.CONTAINS, "SPDXRef-File"),
170+
Relationship("SPDXRef-Package-B", RelationshipType.CONTAINS, "SPDXRef-File"),
171+
]
172+
document = Document(
173+
creation_info=creation_info,
174+
packages=[package_a, package_b],
175+
files=[file],
176+
relationships=relationships,
177+
)
178+
mock: MagicMock = mock_open()
179+
with patch(f"{__name__}.open", mock, create=True):
180+
with open("foo", "w") as file:
181+
write_document(document, file)
182+
183+
mock.assert_called_once_with("foo", "w")
184+
handle = mock()
185+
handle.write.assert_has_calls(
186+
[
187+
call("## Document Information\n"),
188+
call("SPDXVersion: SPDX-2.3\n"),
189+
call("DataLicense: CC0-1.0\n"),
190+
call("SPDXID: SPDXRef-DOCUMENT\n"),
191+
call("DocumentName: SPDX Lite Document\n"),
192+
call("DocumentNamespace: https://test.namespace.com\n"),
193+
call("\n"),
194+
call("## Creation Information\n"),
195+
call("Creator: Person: John Doe\n"),
196+
call("Created: 2023-03-14T08:49:00Z\n"),
197+
call("\n"),
198+
call("## Package Information\n"),
199+
call("PackageName: Example package A\n"),
200+
call("SPDXID: SPDXRef-Package-A\n"),
201+
call("PackageDownloadLocation: https://download.com\n"),
202+
call("FilesAnalyzed: True\n"),
203+
call("\n"),
204+
call("## File Information\n"),
205+
call("FileName: Example file\n"),
206+
call("SPDXID: SPDXRef-File\n"),
207+
call("FileChecksum: SHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12\n"),
208+
call("\n"),
209+
call("## Package Information\n"),
210+
call("PackageName: Example package B\n"),
211+
call("SPDXID: SPDXRef-Package-B\n"),
212+
call("PackageDownloadLocation: https://download.com\n"),
213+
call("FilesAnalyzed: True\n"),
214+
call("\n"),
215+
call("## Relationships\n"),
216+
call("Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-A\n"),
217+
call("Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-B\n"),
218+
call("Relationship: SPDXRef-Package-B CONTAINS SPDXRef-File\n"),
219+
call("\n"),
220+
]
221+
)

0 commit comments

Comments
 (0)