diff options
Diffstat (limited to 'tvsaver/saver2v3')
-rw-r--r-- | tvsaver/saver2v3/save_annotation.go | 32 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_annotation_test.go | 110 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_creation_info.go | 30 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_creation_info_test.go | 112 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_document.go | 104 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_document_test.go | 343 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_file.go | 81 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_file_test.go | 314 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_other_license.go | 32 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_other_license_test.go | 83 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_package.go | 129 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_package_test.go | 531 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_relationship.go | 24 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_relationship_test.go | 145 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_review.go | 26 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_review_test.go | 98 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_snippet.go | 55 | ||||
-rw-r--r-- | tvsaver/saver2v3/save_snippet_test.go | 144 | ||||
-rw-r--r-- | tvsaver/saver2v3/util.go | 16 | ||||
-rw-r--r-- | tvsaver/saver2v3/util_test.go | 32 |
20 files changed, 2441 insertions, 0 deletions
diff --git a/tvsaver/saver2v3/save_annotation.go b/tvsaver/saver2v3/save_annotation.go new file mode 100644 index 0000000..6c4f1aa --- /dev/null +++ b/tvsaver/saver2v3/save_annotation.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderAnnotation2_3(ann *v2_3.Annotation, w io.Writer) error { + if ann.Annotator.Annotator != "" && ann.Annotator.AnnotatorType != "" { + fmt.Fprintf(w, "Annotator: %s: %s\n", ann.Annotator.AnnotatorType, ann.Annotator.Annotator) + } + if ann.AnnotationDate != "" { + fmt.Fprintf(w, "AnnotationDate: %s\n", ann.AnnotationDate) + } + if ann.AnnotationType != "" { + fmt.Fprintf(w, "AnnotationType: %s\n", ann.AnnotationType) + } + annIDStr := common.RenderDocElementID(ann.AnnotationSPDXIdentifier) + if annIDStr != "SPDXRef-" { + fmt.Fprintf(w, "SPDXREF: %s\n", annIDStr) + } + if ann.AnnotationComment != "" { + fmt.Fprintf(w, "AnnotationComment: %s\n", textify(ann.AnnotationComment)) + } + + return nil +} diff --git a/tvsaver/saver2v3/save_annotation_test.go b/tvsaver/saver2v3/save_annotation_test.go new file mode 100644 index 0000000..7471260 --- /dev/null +++ b/tvsaver/saver2v3/save_annotation_test.go @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Annotation section Saver tests ===== +func TestSaver2_3AnnotationSavesTextForPerson(t *testing.T) { + ann := &v2_3.Annotation{ + Annotator: common.Annotator{AnnotatorType: "Person", Annotator: "John Doe"}, + AnnotationDate: "2018-10-10T17:52:00Z", + AnnotationType: "REVIEW", + AnnotationSPDXIdentifier: common.MakeDocElementID("", "DOCUMENT"), + AnnotationComment: "This is an annotation about the SPDX document", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString(`Annotator: Person: John Doe +AnnotationDate: 2018-10-10T17:52:00Z +AnnotationType: REVIEW +SPDXREF: SPDXRef-DOCUMENT +AnnotationComment: This is an annotation about the SPDX document +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderAnnotation2_3(ann, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3AnnotationSavesTextForOrganization(t *testing.T) { + ann := &v2_3.Annotation{ + Annotator: common.Annotator{AnnotatorType: "Organization", Annotator: "John Doe, Inc."}, + AnnotationDate: "2018-10-10T17:52:00Z", + AnnotationType: "REVIEW", + AnnotationSPDXIdentifier: common.MakeDocElementID("", "DOCUMENT"), + AnnotationComment: "This is an annotation about the SPDX document", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString(`Annotator: Organization: John Doe, Inc. +AnnotationDate: 2018-10-10T17:52:00Z +AnnotationType: REVIEW +SPDXREF: SPDXRef-DOCUMENT +AnnotationComment: This is an annotation about the SPDX document +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderAnnotation2_3(ann, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3AnnotationSavesTextForTool(t *testing.T) { + ann := &v2_3.Annotation{ + Annotator: common.Annotator{AnnotatorType: "Tool", Annotator: "magictool-1.1"}, + AnnotationDate: "2018-10-10T17:52:00Z", + AnnotationType: "REVIEW", + AnnotationSPDXIdentifier: common.MakeDocElementID("", "DOCUMENT"), + AnnotationComment: "This is an annotation about the SPDX document", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString(`Annotator: Tool: magictool-1.1 +AnnotationDate: 2018-10-10T17:52:00Z +AnnotationType: REVIEW +SPDXREF: SPDXRef-DOCUMENT +AnnotationComment: This is an annotation about the SPDX document +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderAnnotation2_3(ann, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +// note that the annotation has no optional or multiple fields diff --git a/tvsaver/saver2v3/save_creation_info.go b/tvsaver/saver2v3/save_creation_info.go new file mode 100644 index 0000000..2e8037d --- /dev/null +++ b/tvsaver/saver2v3/save_creation_info.go @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderCreationInfo2_3(ci *v2_3.CreationInfo, w io.Writer) error { + if ci.LicenseListVersion != "" { + fmt.Fprintf(w, "LicenseListVersion: %s\n", ci.LicenseListVersion) + } + for _, creator := range ci.Creators { + fmt.Fprintf(w, "Creator: %s: %s\n", creator.CreatorType, creator.Creator) + } + if ci.Created != "" { + fmt.Fprintf(w, "Created: %s\n", ci.Created) + } + if ci.CreatorComment != "" { + fmt.Fprintf(w, "CreatorComment: %s\n", textify(ci.CreatorComment)) + } + + // add blank newline b/c end of a main section + fmt.Fprintf(w, "\n") + + return nil +} diff --git a/tvsaver/saver2v3/save_creation_info_test.go b/tvsaver/saver2v3/save_creation_info_test.go new file mode 100644 index 0000000..a433dc5 --- /dev/null +++ b/tvsaver/saver2v3/save_creation_info_test.go @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Creation Info section Saver tests ===== +func TestSaver2_3CISavesText(t *testing.T) { + ci := &v2_3.CreationInfo{ + LicenseListVersion: "3.9", + Creators: []common.Creator{ + {Creator: "John Doe", CreatorType: "Person"}, + {Creator: "Jane Doe (janedoe@example.com)", CreatorType: "Person"}, + {Creator: "John Doe, Inc.", CreatorType: "Organization"}, + {Creator: "Jane Doe LLC", CreatorType: "Organization"}, + {Creator: "magictool1-1.0", CreatorType: "Tool"}, + {Creator: "magictool2-1.0", CreatorType: "Tool"}, + {Creator: "magictool3-1.0", CreatorType: "Tool"}, + }, + Created: "2018-10-10T06:20:00Z", + CreatorComment: "this is a creator comment", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`LicenseListVersion: 3.9 +Creator: Person: John Doe +Creator: Person: Jane Doe (janedoe@example.com) +Creator: Organization: John Doe, Inc. +Creator: Organization: Jane Doe LLC +Creator: Tool: magictool1-1.0 +Creator: Tool: magictool2-1.0 +Creator: Tool: magictool3-1.0 +Created: 2018-10-10T06:20:00Z +CreatorComment: this is a creator comment + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderCreationInfo2_3(ci, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3CIOmitsOptionalFieldsIfEmpty(t *testing.T) { + // --- need at least one creator; do first for Persons --- + ci1 := &v2_3.CreationInfo{ + Creators: []common.Creator{ + {Creator: "John Doe", CreatorType: "Person"}, + }, + Created: "2018-10-10T06:20:00Z", + } + + // what we want to get, as a buffer of bytes + want1 := bytes.NewBufferString(`Creator: Person: John Doe +Created: 2018-10-10T06:20:00Z + +`) + + // render as buffer of bytes + var got1 bytes.Buffer + err := renderCreationInfo2_3(ci1, &got1) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c1 := bytes.Compare(want1.Bytes(), got1.Bytes()) + if c1 != 0 { + t.Errorf("Expected %v, got %v", want1.String(), got1.String()) + } + + // --- need at least one creator; now switch to organization --- + ci2 := &v2_3.CreationInfo{ + Creators: []common.Creator{ + {Creator: "John Doe, Inc.", CreatorType: "Organization"}, + }, + Created: "2018-10-10T06:20:00Z", + } + + // what we want to get, as a buffer of bytes + want2 := bytes.NewBufferString(`Creator: Organization: John Doe, Inc. +Created: 2018-10-10T06:20:00Z + +`) + + // render as buffer of bytes + var got2 bytes.Buffer + err = renderCreationInfo2_3(ci2, &got2) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c2 := bytes.Compare(want2.Bytes(), got2.Bytes()) + if c2 != 0 { + t.Errorf("Expected %v, got %v", want2.String(), got2.String()) + } +} diff --git a/tvsaver/saver2v3/save_document.go b/tvsaver/saver2v3/save_document.go new file mode 100644 index 0000000..e8c2535 --- /dev/null +++ b/tvsaver/saver2v3/save_document.go @@ -0,0 +1,104 @@ +// Package saver2v3 contains functions to render and write a tag-value +// formatted version of an in-memory SPDX document and its sections +// (version 2.2). +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +package saver2v3 + +import ( + "fmt" + "io" + "sort" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// RenderDocument2_3 is the main entry point to take an SPDX in-memory +// Document (version 2.2), and render it to the received io.Writer. +// It is only exported in order to be available to the tvsaver package, +// and typically does not need to be called by client code. +func RenderDocument2_3(doc *v2_3.Document, w io.Writer) error { + if doc.CreationInfo == nil { + return fmt.Errorf("Document had nil CreationInfo section") + } + + if doc.SPDXVersion != "" { + fmt.Fprintf(w, "SPDXVersion: %s\n", doc.SPDXVersion) + } + if doc.DataLicense != "" { + fmt.Fprintf(w, "DataLicense: %s\n", doc.DataLicense) + } + if doc.SPDXIdentifier != "" { + fmt.Fprintf(w, "SPDXID: %s\n", common.RenderElementID(doc.SPDXIdentifier)) + } + if doc.DocumentName != "" { + fmt.Fprintf(w, "DocumentName: %s\n", doc.DocumentName) + } + if doc.DocumentNamespace != "" { + fmt.Fprintf(w, "DocumentNamespace: %s\n", doc.DocumentNamespace) + } + // print EDRs in order sorted by identifier + sort.Slice(doc.ExternalDocumentReferences, func(i, j int) bool { + return doc.ExternalDocumentReferences[i].DocumentRefID < doc.ExternalDocumentReferences[j].DocumentRefID + }) + for _, edr := range doc.ExternalDocumentReferences { + fmt.Fprintf(w, "ExternalDocumentRef: DocumentRef-%s %s %s:%s\n", + edr.DocumentRefID, edr.URI, edr.Checksum.Algorithm, edr.Checksum.Value) + } + if doc.DocumentComment != "" { + fmt.Fprintf(w, "DocumentComment: %s\n", textify(doc.DocumentComment)) + } + + renderCreationInfo2_3(doc.CreationInfo, w) + + if len(doc.Files) > 0 { + fmt.Fprintf(w, "##### Unpackaged files\n\n") + sort.Slice(doc.Files, func(i, j int) bool { + return doc.Files[i].FileSPDXIdentifier < doc.Files[j].FileSPDXIdentifier + }) + for _, fi := range doc.Files { + renderFile2_3(fi, w) + } + } + + // sort Packages by identifier + sort.Slice(doc.Packages, func(i, j int) bool { + return doc.Packages[i].PackageSPDXIdentifier < doc.Packages[j].PackageSPDXIdentifier + }) + for _, pkg := range doc.Packages { + fmt.Fprintf(w, "##### Package: %s\n\n", pkg.PackageName) + renderPackage2_3(pkg, w) + } + + if len(doc.OtherLicenses) > 0 { + fmt.Fprintf(w, "##### Other Licenses\n\n") + for _, ol := range doc.OtherLicenses { + renderOtherLicense2_3(ol, w) + } + } + + if len(doc.Relationships) > 0 { + fmt.Fprintf(w, "##### Relationships\n\n") + for _, rln := range doc.Relationships { + renderRelationship2_3(rln, w) + } + fmt.Fprintf(w, "\n") + } + + if len(doc.Annotations) > 0 { + fmt.Fprintf(w, "##### Annotations\n\n") + for _, ann := range doc.Annotations { + renderAnnotation2_3(ann, w) + fmt.Fprintf(w, "\n") + } + } + + if len(doc.Reviews) > 0 { + fmt.Fprintf(w, "##### Reviews\n\n") + for _, rev := range doc.Reviews { + renderReview2_3(rev, w) + } + } + + return nil +} diff --git a/tvsaver/saver2v3/save_document_test.go b/tvsaver/saver2v3/save_document_test.go new file mode 100644 index 0000000..10aa311 --- /dev/null +++ b/tvsaver/saver2v3/save_document_test.go @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== entire Document Saver tests ===== +func TestSaver2_3DocumentSavesText(t *testing.T) { + + // Creation Info section + ci := &v2_3.CreationInfo{ + Creators: []common.Creator{ + {Creator: "John Doe", CreatorType: "Person"}, + }, + Created: "2018-10-10T06:20:00Z", + } + + // unpackaged files + f1 := &v2_3.File{ + FileName: "/tmp/whatever1.txt", + FileSPDXIdentifier: common.ElementID("File1231"), + Checksums: []common.Checksum{{Value: "85ed0817af83a24ad8da68c2b5094de69833983c", Algorithm: common.SHA1}}, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{"Apache-2.0"}, + FileCopyrightText: "Copyright (c) Jane Doe", + } + + f2 := &v2_3.File{ + FileName: "/tmp/whatever2.txt", + FileSPDXIdentifier: common.ElementID("File1232"), + Checksums: []common.Checksum{{Value: "85ed0817af83a24ad8da68c2b5094de69833983d", Algorithm: common.SHA1}}, + LicenseConcluded: "MIT", + LicenseInfoInFiles: []string{"MIT"}, + FileCopyrightText: "Copyright (c) John Doe", + } + + unFiles := []*v2_3.File{ + f1, + f2, + } + + // Package 1: packaged files with snippets + sn1 := &v2_3.Snippet{ + SnippetSPDXIdentifier: "Snippet19", + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "FileHasSnippets").ElementRefID, + Ranges: []common.SnippetRange{{StartPointer: common.SnippetRangePointer{Offset: 17}, EndPointer: common.SnippetRangePointer{Offset: 209}}}, + SnippetLicenseConcluded: "GPL-2.0-or-later", + SnippetCopyrightText: "Copyright (c) John Doe 20x6", + } + + sn2 := &v2_3.Snippet{ + SnippetSPDXIdentifier: "Snippet20", + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "FileHasSnippets").ElementRefID, + Ranges: []common.SnippetRange{{StartPointer: common.SnippetRangePointer{Offset: 268}, EndPointer: common.SnippetRangePointer{Offset: 309}}}, + SnippetLicenseConcluded: "WTFPL", + SnippetCopyrightText: "NOASSERTION", + } + + f3 := &v2_3.File{ + FileName: "/tmp/file-with-snippets.txt", + FileSPDXIdentifier: common.ElementID("FileHasSnippets"), + Checksums: []common.Checksum{{Value: "85ed0817af83a24ad8da68c2b5094de69833983e", Algorithm: common.SHA1}}, + LicenseConcluded: "GPL-2.0-or-later AND WTFPL", + LicenseInfoInFiles: []string{ + "Apache-2.0", + "GPL-2.0-or-later", + "WTFPL", + }, + FileCopyrightText: "Copyright (c) Jane Doe", + Snippets: map[common.ElementID]*v2_3.Snippet{ + common.ElementID("Snippet19"): sn1, + common.ElementID("Snippet20"): sn2, + }, + } + + f4 := &v2_3.File{ + FileName: "/tmp/another-file.txt", + FileSPDXIdentifier: common.ElementID("FileAnother"), + Checksums: []common.Checksum{{Value: "85ed0817af83a24ad8da68c2b5094de69833983f", Algorithm: common.SHA1}}, + LicenseConcluded: "BSD-3-Clause", + LicenseInfoInFiles: []string{"BSD-3-Clause"}, + FileCopyrightText: "Copyright (c) Jane Doe LLC", + } + + pkgWith := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: true, + IsFilesAnalyzedTagPresent: true, + PackageVerificationCode: &common.PackageVerificationCode{Value: "0123456789abcdef0123456789abcdef01234567"}, + PackageLicenseConcluded: "GPL-2.0-or-later AND BSD-3-Clause AND WTFPL", + PackageLicenseInfoFromFiles: []string{ + "Apache-2.0", + "GPL-2.0-or-later", + "WTFPL", + "BSD-3-Clause", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageCopyrightText: "Copyright (c) John Doe, Inc.", + Files: []*v2_3.File{ + f3, + f4, + }, + } + + // Other Licenses 1 and 2 + ol1 := &v2_3.OtherLicense{ + LicenseIdentifier: "LicenseRef-1", + ExtractedText: `License 1 text +blah blah blah +blah blah blah blah`, + LicenseName: "License 1", + } + + ol2 := &v2_3.OtherLicense{ + LicenseIdentifier: "LicenseRef-2", + ExtractedText: `License 2 text - this is a license that does some stuff`, + LicenseName: "License 2", + } + + // Relationships + rln1 := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "p1"), + Relationship: "DESCRIBES", + } + + rln2 := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "File1231"), + Relationship: "DESCRIBES", + } + + rln3 := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "File1232"), + Relationship: "DESCRIBES", + } + + // Annotations + ann1 := &v2_3.Annotation{ + Annotator: common.Annotator{Annotator: "John Doe", + AnnotatorType: "Person"}, + AnnotationDate: "2018-10-10T17:52:00Z", + AnnotationType: "REVIEW", + AnnotationSPDXIdentifier: common.MakeDocElementID("", "DOCUMENT"), + AnnotationComment: "This is an annotation about the SPDX document", + } + + ann2 := &v2_3.Annotation{ + Annotator: common.Annotator{Annotator: "John Doe, Inc.", + AnnotatorType: "Organization"}, + AnnotationDate: "2018-10-10T17:52:00Z", + AnnotationType: "REVIEW", + AnnotationSPDXIdentifier: common.MakeDocElementID("", "p1"), + AnnotationComment: "This is an annotation about Package p1", + } + + // Reviews + rev1 := &v2_3.Review{ + Reviewer: "John Doe", + ReviewerType: "Person", + ReviewDate: "2018-10-14T10:28:00Z", + } + rev2 := &v2_3.Review{ + Reviewer: "Jane Doe LLC", + ReviewerType: "Organization", + ReviewDate: "2018-10-14T10:28:00Z", + ReviewComment: "I have reviewed this SPDX document and it is awesome", + } + + // now, build the document + doc := &v2_3.Document{ + SPDXVersion: "SPDX-2.2", + DataLicense: "CC0-1.0", + SPDXIdentifier: common.ElementID("DOCUMENT"), + DocumentName: "tools-golang-0.0.1.abcdef", + DocumentNamespace: "https://github.com/spdx/spdx-docs/tools-golang/tools-golang-0.0.1.abcdef.whatever", + CreationInfo: ci, + Packages: []*v2_3.Package{ + pkgWith, + }, + Files: unFiles, + OtherLicenses: []*v2_3.OtherLicense{ + ol1, + ol2, + }, + Relationships: []*v2_3.Relationship{ + rln1, + rln2, + rln3, + }, + Annotations: []*v2_3.Annotation{ + ann1, + ann2, + }, + Reviews: []*v2_3.Review{ + rev1, + rev2, + }, + } + + want := bytes.NewBufferString(`SPDXVersion: SPDX-2.2 +DataLicense: CC0-1.0 +SPDXID: SPDXRef-DOCUMENT +DocumentName: tools-golang-0.0.1.abcdef +DocumentNamespace: https://github.com/spdx/spdx-docs/tools-golang/tools-golang-0.0.1.abcdef.whatever +Creator: Person: John Doe +Created: 2018-10-10T06:20:00Z + +##### Unpackaged files + +FileName: /tmp/whatever1.txt +SPDXID: SPDXRef-File1231 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +FileCopyrightText: Copyright (c) Jane Doe + +FileName: /tmp/whatever2.txt +SPDXID: SPDXRef-File1232 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983d +LicenseConcluded: MIT +LicenseInfoInFile: MIT +FileCopyrightText: Copyright (c) John Doe + +##### Package: p1 + +PackageName: p1 +SPDXID: SPDXRef-p1 +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +FilesAnalyzed: true +PackageVerificationCode: 0123456789abcdef0123456789abcdef01234567 +PackageLicenseConcluded: GPL-2.0-or-later AND BSD-3-Clause AND WTFPL +PackageLicenseInfoFromFiles: Apache-2.0 +PackageLicenseInfoFromFiles: GPL-2.0-or-later +PackageLicenseInfoFromFiles: WTFPL +PackageLicenseInfoFromFiles: BSD-3-Clause +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageCopyrightText: Copyright (c) John Doe, Inc. + +FileName: /tmp/another-file.txt +SPDXID: SPDXRef-FileAnother +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983f +LicenseConcluded: BSD-3-Clause +LicenseInfoInFile: BSD-3-Clause +FileCopyrightText: Copyright (c) Jane Doe LLC + +FileName: /tmp/file-with-snippets.txt +SPDXID: SPDXRef-FileHasSnippets +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983e +LicenseConcluded: GPL-2.0-or-later AND WTFPL +LicenseInfoInFile: Apache-2.0 +LicenseInfoInFile: GPL-2.0-or-later +LicenseInfoInFile: WTFPL +FileCopyrightText: Copyright (c) Jane Doe + +SnippetSPDXID: SPDXRef-Snippet19 +SnippetFromFileSPDXID: SPDXRef-FileHasSnippets +SnippetByteRange: 17:209 +SnippetLicenseConcluded: GPL-2.0-or-later +SnippetCopyrightText: Copyright (c) John Doe 20x6 + +SnippetSPDXID: SPDXRef-Snippet20 +SnippetFromFileSPDXID: SPDXRef-FileHasSnippets +SnippetByteRange: 268:309 +SnippetLicenseConcluded: WTFPL +SnippetCopyrightText: NOASSERTION + +##### Other Licenses + +LicenseID: LicenseRef-1 +ExtractedText: <text>License 1 text +blah blah blah +blah blah blah blah</text> +LicenseName: License 1 + +LicenseID: LicenseRef-2 +ExtractedText: License 2 text - this is a license that does some stuff +LicenseName: License 2 + +##### Relationships + +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-p1 +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-File1231 +Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-File1232 + +##### Annotations + +Annotator: Person: John Doe +AnnotationDate: 2018-10-10T17:52:00Z +AnnotationType: REVIEW +SPDXREF: SPDXRef-DOCUMENT +AnnotationComment: This is an annotation about the SPDX document + +Annotator: Organization: John Doe, Inc. +AnnotationDate: 2018-10-10T17:52:00Z +AnnotationType: REVIEW +SPDXREF: SPDXRef-p1 +AnnotationComment: This is an annotation about Package p1 + +##### Reviews + +Reviewer: Person: John Doe +ReviewDate: 2018-10-14T10:28:00Z + +Reviewer: Organization: Jane Doe LLC +ReviewDate: 2018-10-14T10:28:00Z +ReviewComment: I have reviewed this SPDX document and it is awesome + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := RenderDocument2_3(doc, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected {{{%v}}}, got {{{%v}}}", want.String(), got.String()) + } + +} + +func TestSaver2_3DocumentReturnsErrorIfNilCreationInfo(t *testing.T) { + doc := &v2_3.Document{} + + var got bytes.Buffer + err := RenderDocument2_3(doc, &got) + if err == nil { + t.Errorf("Expected error, got nil") + } +} diff --git a/tvsaver/saver2v3/save_file.go b/tvsaver/saver2v3/save_file.go new file mode 100644 index 0000000..18a6d98 --- /dev/null +++ b/tvsaver/saver2v3/save_file.go @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + "sort" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderFile2_3(f *v2_3.File, w io.Writer) error { + if f.FileName != "" { + fmt.Fprintf(w, "FileName: %s\n", f.FileName) + } + if f.FileSPDXIdentifier != "" { + fmt.Fprintf(w, "SPDXID: %s\n", common.RenderElementID(f.FileSPDXIdentifier)) + } + for _, s := range f.FileTypes { + fmt.Fprintf(w, "FileType: %s\n", s) + } + + for _, checksum := range f.Checksums { + fmt.Fprintf(w, "FileChecksum: %s: %s\n", checksum.Algorithm, checksum.Value) + } + + if f.LicenseConcluded != "" { + fmt.Fprintf(w, "LicenseConcluded: %s\n", f.LicenseConcluded) + } + for _, s := range f.LicenseInfoInFiles { + fmt.Fprintf(w, "LicenseInfoInFile: %s\n", s) + } + if f.LicenseComments != "" { + fmt.Fprintf(w, "LicenseComments: %s\n", textify(f.LicenseComments)) + } + if f.FileCopyrightText != "" { + fmt.Fprintf(w, "FileCopyrightText: %s\n", textify(f.FileCopyrightText)) + } + for _, aop := range f.ArtifactOfProjects { + fmt.Fprintf(w, "ArtifactOfProjectName: %s\n", aop.Name) + if aop.HomePage != "" { + fmt.Fprintf(w, "ArtifactOfProjectHomePage: %s\n", aop.HomePage) + } + if aop.URI != "" { + fmt.Fprintf(w, "ArtifactOfProjectURI: %s\n", aop.URI) + } + } + if f.FileComment != "" { + fmt.Fprintf(w, "FileComment: %s\n", textify(f.FileComment)) + } + if f.FileNotice != "" { + fmt.Fprintf(w, "FileNotice: %s\n", textify(f.FileNotice)) + } + for _, s := range f.FileContributors { + fmt.Fprintf(w, "FileContributor: %s\n", s) + } + for _, s := range f.FileAttributionTexts { + fmt.Fprintf(w, "FileAttributionText: %s\n", textify(s)) + } + for _, s := range f.FileDependencies { + fmt.Fprintf(w, "FileDependency: %s\n", s) + } + + fmt.Fprintf(w, "\n") + + // also render any snippets for this file + // get slice of Snippet identifiers so we can sort them + snippetKeys := []string{} + for k := range f.Snippets { + snippetKeys = append(snippetKeys, string(k)) + } + sort.Strings(snippetKeys) + for _, sID := range snippetKeys { + s := f.Snippets[common.ElementID(sID)] + renderSnippet2_3(s, w) + } + + return nil +} diff --git a/tvsaver/saver2v3/save_file_test.go b/tvsaver/saver2v3/save_file_test.go new file mode 100644 index 0000000..05fc2cf --- /dev/null +++ b/tvsaver/saver2v3/save_file_test.go @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== File section Saver tests ===== +func TestSaver2_3FileSavesText(t *testing.T) { + f := &v2_3.File{ + FileName: "/tmp/whatever.txt", + FileSPDXIdentifier: common.ElementID("File123"), + FileTypes: []string{ + "TEXT", + "DOCUMENTATION", + }, + Checksums: []common.Checksum{ + {Algorithm: common.SHA1, Value: "85ed0817af83a24ad8da68c2b5094de69833983c"}, + {Algorithm: common.SHA256, Value: "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"}, + {Algorithm: common.MD5, Value: "624c1abb3664f4b35547e7c73864ad24"}, + }, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{ + "Apache-2.0", + "Apache-1.1", + }, + LicenseComments: "this is a license comment(s)", + FileCopyrightText: "Copyright (c) Jane Doe", + ArtifactOfProjects: []*v2_3.ArtifactOfProject{ + { + Name: "project1", + HomePage: "http://example.com/1/", + URI: "http://example.com/1/uri.whatever", + }, + { + Name: "project2", + }, + { + Name: "project3", + HomePage: "http://example.com/3/", + }, + { + Name: "project4", + URI: "http://example.com/4/uri.whatever", + }, + }, + FileComment: "this is a file comment", + FileNotice: "This file may be used under either Apache-2.0 or Apache-1.1.", + FileContributors: []string{ + "John Doe jdoe@example.com", + "EvilCorp", + }, + FileAttributionTexts: []string{ + "attributions", + `multi-line +attribution`, + }, + FileDependencies: []string{ + "f-1.txt", + "g.txt", + }, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`FileName: /tmp/whatever.txt +SPDXID: SPDXRef-File123 +FileType: TEXT +FileType: DOCUMENTATION +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +FileChecksum: SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd +FileChecksum: MD5: 624c1abb3664f4b35547e7c73864ad24 +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +LicenseInfoInFile: Apache-1.1 +LicenseComments: this is a license comment(s) +FileCopyrightText: Copyright (c) Jane Doe +ArtifactOfProjectName: project1 +ArtifactOfProjectHomePage: http://example.com/1/ +ArtifactOfProjectURI: http://example.com/1/uri.whatever +ArtifactOfProjectName: project2 +ArtifactOfProjectName: project3 +ArtifactOfProjectHomePage: http://example.com/3/ +ArtifactOfProjectName: project4 +ArtifactOfProjectURI: http://example.com/4/uri.whatever +FileComment: this is a file comment +FileNotice: This file may be used under either Apache-2.0 or Apache-1.1. +FileContributor: John Doe jdoe@example.com +FileContributor: EvilCorp +FileAttributionText: attributions +FileAttributionText: <text>multi-line +attribution</text> +FileDependency: f-1.txt +FileDependency: g.txt + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderFile2_3(f, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3FileSavesSnippetsAlso(t *testing.T) { + sn1 := &v2_3.Snippet{ + SnippetSPDXIdentifier: common.ElementID("Snippet19"), + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "File123").ElementRefID, + Ranges: []common.SnippetRange{{StartPointer: common.SnippetRangePointer{Offset: 17}, EndPointer: common.SnippetRangePointer{Offset: 209}}}, + SnippetLicenseConcluded: "GPL-2.0-or-later", + SnippetCopyrightText: "Copyright (c) John Doe 20x6", + } + + sn2 := &v2_3.Snippet{ + SnippetSPDXIdentifier: common.ElementID("Snippet20"), + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "File123").ElementRefID, + Ranges: []common.SnippetRange{{StartPointer: common.SnippetRangePointer{Offset: 268}, EndPointer: common.SnippetRangePointer{Offset: 309}}}, + SnippetLicenseConcluded: "WTFPL", + SnippetCopyrightText: "NOASSERTION", + } + + sns := map[common.ElementID]*v2_3.Snippet{ + common.ElementID("Snippet19"): sn1, + common.ElementID("Snippet20"): sn2, + } + + f := &v2_3.File{ + FileName: "/tmp/whatever.txt", + FileSPDXIdentifier: common.ElementID("File123"), + Checksums: []common.Checksum{ + {Algorithm: common.SHA1, Value: "85ed0817af83a24ad8da68c2b5094de69833983c"}, + }, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{ + "Apache-2.0", + }, + FileCopyrightText: "Copyright (c) Jane Doe", + Snippets: sns, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`FileName: /tmp/whatever.txt +SPDXID: SPDXRef-File123 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +FileCopyrightText: Copyright (c) Jane Doe + +SnippetSPDXID: SPDXRef-Snippet19 +SnippetFromFileSPDXID: SPDXRef-File123 +SnippetByteRange: 17:209 +SnippetLicenseConcluded: GPL-2.0-or-later +SnippetCopyrightText: Copyright (c) John Doe 20x6 + +SnippetSPDXID: SPDXRef-Snippet20 +SnippetFromFileSPDXID: SPDXRef-File123 +SnippetByteRange: 268:309 +SnippetLicenseConcluded: WTFPL +SnippetCopyrightText: NOASSERTION + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderFile2_3(f, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3FileOmitsOptionalFieldsIfEmpty(t *testing.T) { + f := &v2_3.File{ + FileName: "/tmp/whatever.txt", + FileSPDXIdentifier: common.ElementID("File123"), + Checksums: []common.Checksum{ + {Algorithm: common.SHA1, Value: "85ed0817af83a24ad8da68c2b5094de69833983c"}, + }, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{ + "Apache-2.0", + }, + FileCopyrightText: "Copyright (c) Jane Doe", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`FileName: /tmp/whatever.txt +SPDXID: SPDXRef-File123 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +FileCopyrightText: Copyright (c) Jane Doe + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderFile2_3(f, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3FileWrapsCopyrightMultiLine(t *testing.T) { + f := &v2_3.File{ + FileName: "/tmp/whatever.txt", + FileSPDXIdentifier: common.ElementID("File123"), + Checksums: []common.Checksum{ + {Algorithm: common.SHA1, Value: "85ed0817af83a24ad8da68c2b5094de69833983c"}, + }, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{ + "Apache-2.0", + }, + FileCopyrightText: `Copyright (c) Jane Doe +Copyright (c) John Doe`, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`FileName: /tmp/whatever.txt +SPDXID: SPDXRef-File123 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +FileCopyrightText: <text>Copyright (c) Jane Doe +Copyright (c) John Doe</text> + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderFile2_3(f, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3FileWrapsCommentsAndNoticesMultiLine(t *testing.T) { + f := &v2_3.File{ + FileName: "/tmp/whatever.txt", + FileSPDXIdentifier: common.ElementID("File123"), + Checksums: []common.Checksum{ + {Algorithm: common.SHA1, Value: "85ed0817af83a24ad8da68c2b5094de69833983c"}, + }, + LicenseComments: `this is a +multi-line license comment`, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{ + "Apache-2.0", + }, + FileCopyrightText: "Copyright (c) Jane Doe", + FileComment: `this is a +multi-line file comment`, + FileNotice: `This file may be used +under either Apache-2.0 or Apache-1.1.`, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`FileName: /tmp/whatever.txt +SPDXID: SPDXRef-File123 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +LicenseComments: <text>this is a +multi-line license comment</text> +FileCopyrightText: Copyright (c) Jane Doe +FileComment: <text>this is a +multi-line file comment</text> +FileNotice: <text>This file may be used +under either Apache-2.0 or Apache-1.1.</text> + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderFile2_3(f, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} diff --git a/tvsaver/saver2v3/save_other_license.go b/tvsaver/saver2v3/save_other_license.go new file mode 100644 index 0000000..9351108 --- /dev/null +++ b/tvsaver/saver2v3/save_other_license.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderOtherLicense2_3(ol *v2_3.OtherLicense, w io.Writer) error { + if ol.LicenseIdentifier != "" { + fmt.Fprintf(w, "LicenseID: %s\n", ol.LicenseIdentifier) + } + if ol.ExtractedText != "" { + fmt.Fprintf(w, "ExtractedText: %s\n", textify(ol.ExtractedText)) + } + if ol.LicenseName != "" { + fmt.Fprintf(w, "LicenseName: %s\n", ol.LicenseName) + } + for _, s := range ol.LicenseCrossReferences { + fmt.Fprintf(w, "LicenseCrossReference: %s\n", s) + } + if ol.LicenseComment != "" { + fmt.Fprintf(w, "LicenseComment: %s\n", textify(ol.LicenseComment)) + } + + fmt.Fprintf(w, "\n") + + return nil +} diff --git a/tvsaver/saver2v3/save_other_license_test.go b/tvsaver/saver2v3/save_other_license_test.go new file mode 100644 index 0000000..00134dd --- /dev/null +++ b/tvsaver/saver2v3/save_other_license_test.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Other License section Saver tests ===== +func TestSaver2_3OtherLicenseSavesText(t *testing.T) { + ol := &v2_3.OtherLicense{ + LicenseIdentifier: "LicenseRef-1", + ExtractedText: `License 1 text +blah blah blah +blah blah blah blah`, + LicenseName: "License 1", + LicenseCrossReferences: []string{ + "http://example.com/License1/", + "http://example.com/License1AnotherURL/", + }, + LicenseComment: "this is a license comment", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`LicenseID: LicenseRef-1 +ExtractedText: <text>License 1 text +blah blah blah +blah blah blah blah</text> +LicenseName: License 1 +LicenseCrossReference: http://example.com/License1/ +LicenseCrossReference: http://example.com/License1AnotherURL/ +LicenseComment: this is a license comment + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderOtherLicense2_3(ol, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3OtherLicenseOmitsOptionalFieldsIfEmpty(t *testing.T) { + ol := &v2_3.OtherLicense{ + LicenseIdentifier: "LicenseRef-1", + ExtractedText: `License 1 text +blah blah blah +blah blah blah blah`, + LicenseName: "License 1", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`LicenseID: LicenseRef-1 +ExtractedText: <text>License 1 text +blah blah blah +blah blah blah blah</text> +LicenseName: License 1 + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderOtherLicense2_3(ol, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} diff --git a/tvsaver/saver2v3/save_package.go b/tvsaver/saver2v3/save_package.go new file mode 100644 index 0000000..d323af9 --- /dev/null +++ b/tvsaver/saver2v3/save_package.go @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderPackage2_3(pkg *v2_3.Package, w io.Writer) error { + if pkg.PackageName != "" { + fmt.Fprintf(w, "PackageName: %s\n", pkg.PackageName) + } + if pkg.PackageSPDXIdentifier != "" { + fmt.Fprintf(w, "SPDXID: %s\n", common.RenderElementID(pkg.PackageSPDXIdentifier)) + } + if pkg.PackageVersion != "" { + fmt.Fprintf(w, "PackageVersion: %s\n", pkg.PackageVersion) + } + if pkg.PackageFileName != "" { + fmt.Fprintf(w, "PackageFileName: %s\n", pkg.PackageFileName) + } + if pkg.PackageSupplier != nil && pkg.PackageSupplier.Supplier != "" { + if pkg.PackageSupplier.SupplierType == "" { + fmt.Fprintf(w, "PackageSupplier: %s\n", pkg.PackageSupplier.Supplier) + } else { + fmt.Fprintf(w, "PackageSupplier: %s: %s\n", pkg.PackageSupplier.SupplierType, pkg.PackageSupplier.Supplier) + } + } + if pkg.PackageOriginator != nil && pkg.PackageOriginator.Originator != "" { + if pkg.PackageOriginator.OriginatorType == "" { + fmt.Fprintf(w, "PackageOriginator: %s\n", pkg.PackageOriginator.Originator) + } else { + fmt.Fprintf(w, "PackageOriginator: %s: %s\n", pkg.PackageOriginator.OriginatorType, pkg.PackageOriginator.Originator) + } + } + if pkg.PackageDownloadLocation != "" { + fmt.Fprintf(w, "PackageDownloadLocation: %s\n", pkg.PackageDownloadLocation) + } + if pkg.PrimaryPackagePurpose != "" { + fmt.Fprintf(w, "PrimaryPackagePurpose: %s\n", pkg.PrimaryPackagePurpose) + } + if pkg.ReleaseDate != "" { + fmt.Fprintf(w, "ReleaseDate: %s\n", pkg.ReleaseDate) + } + if pkg.BuiltDate != "" { + fmt.Fprintf(w, "BuiltDate: %s\n", pkg.BuiltDate) + } + if pkg.ValidUntilDate != "" { + fmt.Fprintf(w, "ValidUntilDate: %s\n", pkg.ValidUntilDate) + } + if pkg.FilesAnalyzed == true { + if pkg.IsFilesAnalyzedTagPresent == true { + fmt.Fprintf(w, "FilesAnalyzed: true\n") + } + } else { + fmt.Fprintf(w, "FilesAnalyzed: false\n") + } + if pkg.PackageVerificationCode != nil && pkg.PackageVerificationCode.Value != "" && pkg.FilesAnalyzed == true { + if len(pkg.PackageVerificationCode.ExcludedFiles) == 0 { + fmt.Fprintf(w, "PackageVerificationCode: %s\n", pkg.PackageVerificationCode.Value) + } else { + fmt.Fprintf(w, "PackageVerificationCode: %s (excludes: %s)\n", pkg.PackageVerificationCode.Value, strings.Join(pkg.PackageVerificationCode.ExcludedFiles, ", ")) + } + } + + for _, checksum := range pkg.PackageChecksums { + fmt.Fprintf(w, "PackageChecksum: %s: %s\n", checksum.Algorithm, checksum.Value) + } + + if pkg.PackageHomePage != "" { + fmt.Fprintf(w, "PackageHomePage: %s\n", pkg.PackageHomePage) + } + if pkg.PackageSourceInfo != "" { + fmt.Fprintf(w, "PackageSourceInfo: %s\n", textify(pkg.PackageSourceInfo)) + } + if pkg.PackageLicenseConcluded != "" { + fmt.Fprintf(w, "PackageLicenseConcluded: %s\n", pkg.PackageLicenseConcluded) + } + if pkg.FilesAnalyzed == true { + for _, s := range pkg.PackageLicenseInfoFromFiles { + fmt.Fprintf(w, "PackageLicenseInfoFromFiles: %s\n", s) + } + } + if pkg.PackageLicenseDeclared != "" { + fmt.Fprintf(w, "PackageLicenseDeclared: %s\n", pkg.PackageLicenseDeclared) + } + if pkg.PackageLicenseComments != "" { + fmt.Fprintf(w, "PackageLicenseComments: %s\n", textify(pkg.PackageLicenseComments)) + } + if pkg.PackageCopyrightText != "" { + fmt.Fprintf(w, "PackageCopyrightText: %s\n", textify(pkg.PackageCopyrightText)) + } + if pkg.PackageSummary != "" { + fmt.Fprintf(w, "PackageSummary: %s\n", textify(pkg.PackageSummary)) + } + if pkg.PackageDescription != "" { + fmt.Fprintf(w, "PackageDescription: %s\n", textify(pkg.PackageDescription)) + } + if pkg.PackageComment != "" { + fmt.Fprintf(w, "PackageComment: %s\n", textify(pkg.PackageComment)) + } + for _, s := range pkg.PackageExternalReferences { + fmt.Fprintf(w, "ExternalRef: %s %s %s\n", s.Category, s.RefType, s.Locator) + if s.ExternalRefComment != "" { + fmt.Fprintf(w, "ExternalRefComment: %s\n", textify(s.ExternalRefComment)) + } + } + for _, s := range pkg.PackageAttributionTexts { + fmt.Fprintf(w, "PackageAttributionText: %s\n", textify(s)) + } + + fmt.Fprintf(w, "\n") + + // also render any files for this package + sort.Slice(pkg.Files, func(i, j int) bool { + return pkg.Files[i].FileSPDXIdentifier < pkg.Files[j].FileSPDXIdentifier + }) + for _, fi := range pkg.Files { + renderFile2_3(fi, w) + } + + return nil +} diff --git a/tvsaver/saver2v3/save_package_test.go b/tvsaver/saver2v3/save_package_test.go new file mode 100644 index 0000000..435b5b5 --- /dev/null +++ b/tvsaver/saver2v3/save_package_test.go @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Package section Saver tests ===== +func TestSaver2_3PackageSavesTextCombo1(t *testing.T) { + // include package external refs + // test Supplier:Organization, Originator:Person + // FilesAnalyzed true, IsFilesAnalyzedTagPresent true + // PackageVerificationCodeExcludedFile has string + + // NOTE, this is an entirely made up CPE and the format is likely invalid + per1 := &v2_3.PackageExternalReference{ + Category: "SECURITY", + RefType: "cpe22Type", + Locator: "cpe:/a:john_doe_inc:p1:0.1.0", + ExternalRefComment: "this is an external ref comment #1", + } + + // NOTE, this is an entirely made up NPM + per2 := &v2_3.PackageExternalReference{ + Category: "PACKAGE-MANAGER", + RefType: "npm", + Locator: "p1@0.1.0", + ExternalRefComment: `this is a +multi-line external ref comment`, + } + + // NOTE, this is an entirely made up SWH persistent ID + per3 := &v2_3.PackageExternalReference{ + Category: "PERSISTENT-ID", + RefType: "swh", + Locator: "swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2", + // no ExternalRefComment for this one + } + + per4 := &v2_3.PackageExternalReference{ + Category: "OTHER", + RefType: "anything", + Locator: "anything-without-spaces-can-go-here", + // no ExternalRefComment for this one + } + + pkg := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageVersion: "0.1.0", + PackageFileName: "p1-0.1.0-master.tar.gz", + PackageSupplier: &common.Supplier{SupplierType: "Organization", Supplier: "John Doe, Inc."}, + PackageOriginator: &common.Originator{Originator: "John Doe", OriginatorType: "Person"}, + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: true, + IsFilesAnalyzedTagPresent: true, + PackageVerificationCode: &common.PackageVerificationCode{ + Value: "0123456789abcdef0123456789abcdef01234567", + ExcludedFiles: []string{"p1-0.1.0.spdx"}, + }, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "85ed0817af83a24ad8da68c2b5094de69833983c", + }, + { + Algorithm: common.SHA256, + Value: "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", + }, + { + Algorithm: common.MD5, + Value: "624c1abb3664f4b35547e7c73864ad24", + }, + }, + PackageHomePage: "http://example.com/p1", + PackageSourceInfo: "this is a source comment", + PackageLicenseConcluded: "GPL-2.0-or-later", + PackageLicenseInfoFromFiles: []string{ + "Apache-1.1", + "Apache-2.0", + "GPL-2.0-or-later", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageLicenseComments: "this is a license comment(s)", + PackageCopyrightText: "Copyright (c) John Doe, Inc.", + PackageSummary: "this is a summary comment", + PackageDescription: "this is a description comment", + PackageComment: "this is a comment comment", + PackageAttributionTexts: []string{"Include this notice in all advertising materials"}, + PackageExternalReferences: []*v2_3.PackageExternalReference{ + per1, + per2, + per3, + per4, + }, + PrimaryPackagePurpose: "LIBRARY", + BuiltDate: "2021-09-15T02:38:00Z", + ValidUntilDate: "2022-10-15T02:38:00Z", + ReleaseDate: "2021-10-15T02:38:00Z", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`PackageName: p1 +SPDXID: SPDXRef-p1 +PackageVersion: 0.1.0 +PackageFileName: p1-0.1.0-master.tar.gz +PackageSupplier: Organization: John Doe, Inc. +PackageOriginator: Person: John Doe +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +PrimaryPackagePurpose: LIBRARY +ReleaseDate: 2021-10-15T02:38:00Z +BuiltDate: 2021-09-15T02:38:00Z +ValidUntilDate: 2022-10-15T02:38:00Z +FilesAnalyzed: true +PackageVerificationCode: 0123456789abcdef0123456789abcdef01234567 (excludes: p1-0.1.0.spdx) +PackageChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +PackageChecksum: SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd +PackageChecksum: MD5: 624c1abb3664f4b35547e7c73864ad24 +PackageHomePage: http://example.com/p1 +PackageSourceInfo: this is a source comment +PackageLicenseConcluded: GPL-2.0-or-later +PackageLicenseInfoFromFiles: Apache-1.1 +PackageLicenseInfoFromFiles: Apache-2.0 +PackageLicenseInfoFromFiles: GPL-2.0-or-later +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageLicenseComments: this is a license comment(s) +PackageCopyrightText: Copyright (c) John Doe, Inc. +PackageSummary: this is a summary comment +PackageDescription: this is a description comment +PackageComment: this is a comment comment +ExternalRef: SECURITY cpe22Type cpe:/a:john_doe_inc:p1:0.1.0 +ExternalRefComment: this is an external ref comment #1 +ExternalRef: PACKAGE-MANAGER npm p1@0.1.0 +ExternalRefComment: <text>this is a +multi-line external ref comment</text> +ExternalRef: PERSISTENT-ID swh swh:1:cnt:94a9ed024d3859793618152ea559a168bbcbb5e2 +ExternalRef: OTHER anything anything-without-spaces-can-go-here +PackageAttributionText: Include this notice in all advertising materials + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderPackage2_3(pkg, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3PackageSavesTextCombo2(t *testing.T) { + // no package external refs + // test Supplier:NOASSERTION, Originator:Organization + // FilesAnalyzed true, IsFilesAnalyzedTagPresent false + // PackageVerificationCodeExcludedFile is empty + + pkg := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageVersion: "0.1.0", + PackageFileName: "p1-0.1.0-master.tar.gz", + PackageSupplier: &common.Supplier{Supplier: "NOASSERTION"}, + PackageOriginator: &common.Originator{OriginatorType: "Organization", Originator: "John Doe, Inc."}, + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: true, + IsFilesAnalyzedTagPresent: false, + PackageVerificationCode: &common.PackageVerificationCode{Value: "0123456789abcdef0123456789abcdef01234567"}, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "85ed0817af83a24ad8da68c2b5094de69833983c", + }, + { + Algorithm: common.SHA256, + Value: "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", + }, + { + Algorithm: common.MD5, + Value: "624c1abb3664f4b35547e7c73864ad24", + }, + }, + PackageHomePage: "http://example.com/p1", + PackageSourceInfo: "this is a source comment", + PackageLicenseConcluded: "GPL-2.0-or-later", + PackageLicenseInfoFromFiles: []string{ + "Apache-1.1", + "Apache-2.0", + "GPL-2.0-or-later", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageLicenseComments: "this is a license comment(s)", + PackageCopyrightText: "Copyright (c) John Doe, Inc.", + PackageSummary: "this is a summary comment", + PackageDescription: "this is a description comment", + PackageComment: "this is a comment comment", + PackageAttributionTexts: []string{"Include this notice in all advertising materials"}, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`PackageName: p1 +SPDXID: SPDXRef-p1 +PackageVersion: 0.1.0 +PackageFileName: p1-0.1.0-master.tar.gz +PackageSupplier: NOASSERTION +PackageOriginator: Organization: John Doe, Inc. +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +PackageVerificationCode: 0123456789abcdef0123456789abcdef01234567 +PackageChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +PackageChecksum: SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd +PackageChecksum: MD5: 624c1abb3664f4b35547e7c73864ad24 +PackageHomePage: http://example.com/p1 +PackageSourceInfo: this is a source comment +PackageLicenseConcluded: GPL-2.0-or-later +PackageLicenseInfoFromFiles: Apache-1.1 +PackageLicenseInfoFromFiles: Apache-2.0 +PackageLicenseInfoFromFiles: GPL-2.0-or-later +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageLicenseComments: this is a license comment(s) +PackageCopyrightText: Copyright (c) John Doe, Inc. +PackageSummary: this is a summary comment +PackageDescription: this is a description comment +PackageComment: this is a comment comment +PackageAttributionText: Include this notice in all advertising materials + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderPackage2_3(pkg, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3PackageSavesTextCombo3(t *testing.T) { + // no package external refs + // test Supplier:Person, Originator:NOASSERTION + // FilesAnalyzed false, IsFilesAnalyzedTagPresent true + // PackageVerificationCodeExcludedFile is empty + // three PackageAttributionTexts, one with multi-line text + + pkg := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageVersion: "0.1.0", + PackageFileName: "p1-0.1.0-master.tar.gz", + PackageSupplier: &common.Supplier{Supplier: "John Doe", SupplierType: "Person"}, + PackageOriginator: &common.Originator{Originator: "NOASSERTION"}, + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: false, + IsFilesAnalyzedTagPresent: true, + // NOTE that verification code MUST be omitted from output + // since FilesAnalyzed is false + PackageVerificationCode: &common.PackageVerificationCode{Value: "0123456789abcdef0123456789abcdef01234567"}, + PackageChecksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "85ed0817af83a24ad8da68c2b5094de69833983c", + }, + { + Algorithm: common.SHA256, + Value: "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd", + }, + { + Algorithm: common.MD5, + Value: "624c1abb3664f4b35547e7c73864ad24", + }, + }, + PackageHomePage: "http://example.com/p1", + PackageSourceInfo: "this is a source comment", + PackageLicenseConcluded: "GPL-2.0-or-later", + // NOTE that license info from files MUST be omitted from output + // since FilesAnalyzed is false + PackageLicenseInfoFromFiles: []string{ + "Apache-1.1", + "Apache-2.0", + "GPL-2.0-or-later", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageLicenseComments: "this is a license comment(s)", + PackageCopyrightText: "Copyright (c) John Doe, Inc.", + PackageSummary: "this is a summary comment", + PackageDescription: "this is a description comment", + PackageComment: "this is a comment comment", + PackageAttributionTexts: []string{ + "Include this notice in all advertising materials", + "and also this notice", + `and this multi-line notice +which goes across two lines`, + }, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`PackageName: p1 +SPDXID: SPDXRef-p1 +PackageVersion: 0.1.0 +PackageFileName: p1-0.1.0-master.tar.gz +PackageSupplier: Person: John Doe +PackageOriginator: NOASSERTION +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +FilesAnalyzed: false +PackageChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +PackageChecksum: SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd +PackageChecksum: MD5: 624c1abb3664f4b35547e7c73864ad24 +PackageHomePage: http://example.com/p1 +PackageSourceInfo: this is a source comment +PackageLicenseConcluded: GPL-2.0-or-later +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageLicenseComments: this is a license comment(s) +PackageCopyrightText: Copyright (c) John Doe, Inc. +PackageSummary: this is a summary comment +PackageDescription: this is a description comment +PackageComment: this is a comment comment +PackageAttributionText: Include this notice in all advertising materials +PackageAttributionText: and also this notice +PackageAttributionText: <text>and this multi-line notice +which goes across two lines</text> + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderPackage2_3(pkg, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3PackageSaveOmitsOptionalFieldsIfEmpty(t *testing.T) { + pkg := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: false, + IsFilesAnalyzedTagPresent: true, + // NOTE that verification code MUST be omitted from output, + // even if present in model, since FilesAnalyzed is false + PackageLicenseConcluded: "GPL-2.0-or-later", + // NOTE that license info from files MUST be omitted from output + // even if present in model, since FilesAnalyzed is false + PackageLicenseInfoFromFiles: []string{ + "Apache-1.1", + "Apache-2.0", + "GPL-2.0-or-later", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageCopyrightText: "Copyright (c) John Doe, Inc.", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`PackageName: p1 +SPDXID: SPDXRef-p1 +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-or-later +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageCopyrightText: Copyright (c) John Doe, Inc. + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderPackage2_3(pkg, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3PackageSavesFilesIfPresent(t *testing.T) { + f1 := &v2_3.File{ + FileName: "/tmp/whatever1.txt", + FileSPDXIdentifier: common.ElementID("File1231"), + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "85ed0817af83a24ad8da68c2b5094de69833983c", + }, + }, + LicenseConcluded: "Apache-2.0", + LicenseInfoInFiles: []string{"Apache-2.0"}, + FileCopyrightText: "Copyright (c) Jane Doe", + } + + f2 := &v2_3.File{ + FileName: "/tmp/whatever2.txt", + FileSPDXIdentifier: common.ElementID("File1232"), + Checksums: []common.Checksum{ + { + Algorithm: common.SHA1, + Value: "85ed0817af83a24ad8da68c2b5094de69833983d", + }, + }, + LicenseConcluded: "MIT", + LicenseInfoInFiles: []string{"MIT"}, + FileCopyrightText: "Copyright (c) John Doe", + } + + pkg := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: false, + IsFilesAnalyzedTagPresent: true, + // NOTE that verification code MUST be omitted from output, + // even if present in model, since FilesAnalyzed is false + PackageLicenseConcluded: "GPL-2.0-or-later", + // NOTE that license info from files MUST be omitted from output + // even if present in model, since FilesAnalyzed is false + PackageLicenseInfoFromFiles: []string{ + "Apache-1.1", + "Apache-2.0", + "GPL-2.0-or-later", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageCopyrightText: "Copyright (c) John Doe, Inc.", + Files: []*v2_3.File{ + f1, + f2, + }, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`PackageName: p1 +SPDXID: SPDXRef-p1 +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-or-later +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageCopyrightText: Copyright (c) John Doe, Inc. + +FileName: /tmp/whatever1.txt +SPDXID: SPDXRef-File1231 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c +LicenseConcluded: Apache-2.0 +LicenseInfoInFile: Apache-2.0 +FileCopyrightText: Copyright (c) Jane Doe + +FileName: /tmp/whatever2.txt +SPDXID: SPDXRef-File1232 +FileChecksum: SHA1: 85ed0817af83a24ad8da68c2b5094de69833983d +LicenseConcluded: MIT +LicenseInfoInFile: MIT +FileCopyrightText: Copyright (c) John Doe + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderPackage2_3(pkg, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3PackageWrapsMultiLine(t *testing.T) { + pkg := &v2_3.Package{ + PackageName: "p1", + PackageSPDXIdentifier: common.ElementID("p1"), + PackageDownloadLocation: "http://example.com/p1/p1-0.1.0-master.tar.gz", + FilesAnalyzed: false, + IsFilesAnalyzedTagPresent: true, + PackageLicenseConcluded: "GPL-2.0-or-later", + PackageLicenseInfoFromFiles: []string{ + "Apache-1.1", + "Apache-2.0", + "GPL-2.0-or-later", + }, + PackageLicenseDeclared: "Apache-2.0 OR GPL-2.0-or-later", + PackageCopyrightText: `Copyright (c) John Doe, Inc. +Copyright Jane Doe`, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`PackageName: p1 +SPDXID: SPDXRef-p1 +PackageDownloadLocation: http://example.com/p1/p1-0.1.0-master.tar.gz +FilesAnalyzed: false +PackageLicenseConcluded: GPL-2.0-or-later +PackageLicenseDeclared: Apache-2.0 OR GPL-2.0-or-later +PackageCopyrightText: <text>Copyright (c) John Doe, Inc. +Copyright Jane Doe</text> + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderPackage2_3(pkg, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} diff --git a/tvsaver/saver2v3/save_relationship.go b/tvsaver/saver2v3/save_relationship.go new file mode 100644 index 0000000..c83310f --- /dev/null +++ b/tvsaver/saver2v3/save_relationship.go @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderRelationship2_3(rln *v2_3.Relationship, w io.Writer) error { + rlnAStr := common.RenderDocElementID(rln.RefA) + rlnBStr := common.RenderDocElementID(rln.RefB) + if rlnAStr != "SPDXRef-" && rlnBStr != "SPDXRef-" && rln.Relationship != "" { + fmt.Fprintf(w, "Relationship: %s %s %s\n", rlnAStr, rln.Relationship, rlnBStr) + } + if rln.RelationshipComment != "" { + fmt.Fprintf(w, "RelationshipComment: %s\n", textify(rln.RelationshipComment)) + } + + return nil +} diff --git a/tvsaver/saver2v3/save_relationship_test.go b/tvsaver/saver2v3/save_relationship_test.go new file mode 100644 index 0000000..26ce0c3 --- /dev/null +++ b/tvsaver/saver2v3/save_relationship_test.go @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Relationship section Saver tests ===== +func TestSaver2_3RelationshipSavesText(t *testing.T) { + rln := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "2"), + Relationship: "DESCRIBES", + RelationshipComment: "this is a comment", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString(`Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-2 +RelationshipComment: this is a comment +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderRelationship2_3(rln, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3RelationshipOmitsOptionalFieldsIfEmpty(t *testing.T) { + rln := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "2"), + Relationship: "DESCRIBES", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString("Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-2\n") + + // render as buffer of bytes + var got bytes.Buffer + err := renderRelationship2_3(rln, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3RelationshipCanHaveNONEOnRight(t *testing.T) { + rln := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "PackageA"), + RefB: common.MakeDocElementSpecial("NONE"), + Relationship: "DEPENDS_ON", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString("Relationship: SPDXRef-PackageA DEPENDS_ON NONE\n") + + // render as buffer of bytes + var got bytes.Buffer + err := renderRelationship2_3(rln, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3RelationshipCanHaveNOASSERTIONOnRight(t *testing.T) { + rln := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "PackageA"), + RefB: common.MakeDocElementSpecial("NOASSERTION"), + Relationship: "DEPENDS_ON", + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString("Relationship: SPDXRef-PackageA DEPENDS_ON NOASSERTION\n") + + // render as buffer of bytes + var got bytes.Buffer + err := renderRelationship2_3(rln, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3RelationshipWrapsCommentMultiLine(t *testing.T) { + rln := &v2_3.Relationship{ + RefA: common.MakeDocElementID("", "DOCUMENT"), + RefB: common.MakeDocElementID("", "2"), + Relationship: "DESCRIBES", + RelationshipComment: `this is a +multi-line comment`, + } + + // what we want to get, as a buffer of bytes + // no trailing blank newline + want := bytes.NewBufferString(`Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-2 +RelationshipComment: <text>this is a +multi-line comment</text> +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderRelationship2_3(rln, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} diff --git a/tvsaver/saver2v3/save_review.go b/tvsaver/saver2v3/save_review.go new file mode 100644 index 0000000..72bac11 --- /dev/null +++ b/tvsaver/saver2v3/save_review.go @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderReview2_3(rev *v2_3.Review, w io.Writer) error { + if rev.Reviewer != "" && rev.ReviewerType != "" { + fmt.Fprintf(w, "Reviewer: %s: %s\n", rev.ReviewerType, rev.Reviewer) + } + if rev.ReviewDate != "" { + fmt.Fprintf(w, "ReviewDate: %s\n", rev.ReviewDate) + } + if rev.ReviewComment != "" { + fmt.Fprintf(w, "ReviewComment: %s\n", textify(rev.ReviewComment)) + } + + fmt.Fprintf(w, "\n") + + return nil +} diff --git a/tvsaver/saver2v3/save_review_test.go b/tvsaver/saver2v3/save_review_test.go new file mode 100644 index 0000000..5eec3bc --- /dev/null +++ b/tvsaver/saver2v3/save_review_test.go @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Review section Saver tests ===== +func TestSaver2_3ReviewSavesText(t *testing.T) { + rev := &v2_3.Review{ + Reviewer: "John Doe", + ReviewerType: "Person", + ReviewDate: "2018-10-14T10:28:00Z", + ReviewComment: "this is a review comment", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`Reviewer: Person: John Doe +ReviewDate: 2018-10-14T10:28:00Z +ReviewComment: this is a review comment + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderReview2_3(rev, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3ReviewOmitsOptionalFieldsIfEmpty(t *testing.T) { + rev := &v2_3.Review{ + Reviewer: "John Doe", + ReviewerType: "Person", + ReviewDate: "2018-10-14T10:28:00Z", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`Reviewer: Person: John Doe +ReviewDate: 2018-10-14T10:28:00Z + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderReview2_3(rev, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3ReviewWrapsMultiLine(t *testing.T) { + rev := &v2_3.Review{ + Reviewer: "John Doe", + ReviewerType: "Person", + ReviewDate: "2018-10-14T10:28:00Z", + ReviewComment: `this is a +multi-line review comment`, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`Reviewer: Person: John Doe +ReviewDate: 2018-10-14T10:28:00Z +ReviewComment: <text>this is a +multi-line review comment</text> + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderReview2_3(rev, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} diff --git a/tvsaver/saver2v3/save_snippet.go b/tvsaver/saver2v3/save_snippet.go new file mode 100644 index 0000000..2ba4a3b --- /dev/null +++ b/tvsaver/saver2v3/save_snippet.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "io" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +func renderSnippet2_3(sn *v2_3.Snippet, w io.Writer) error { + if sn.SnippetSPDXIdentifier != "" { + fmt.Fprintf(w, "SnippetSPDXID: %s\n", common.RenderElementID(sn.SnippetSPDXIdentifier)) + } + snFromFileIDStr := common.RenderElementID(sn.SnippetFromFileSPDXIdentifier) + if snFromFileIDStr != "" { + fmt.Fprintf(w, "SnippetFromFileSPDXID: %s\n", snFromFileIDStr) + } + + for _, snippetRange := range sn.Ranges { + if snippetRange.StartPointer.Offset != 0 && snippetRange.EndPointer.Offset != 0 { + fmt.Fprintf(w, "SnippetByteRange: %d:%d\n", snippetRange.StartPointer.Offset, snippetRange.EndPointer.Offset) + } + if snippetRange.StartPointer.LineNumber != 0 && snippetRange.EndPointer.LineNumber != 0 { + fmt.Fprintf(w, "SnippetLineRange: %d:%d\n", snippetRange.StartPointer.LineNumber, snippetRange.EndPointer.LineNumber) + } + } + if sn.SnippetLicenseConcluded != "" { + fmt.Fprintf(w, "SnippetLicenseConcluded: %s\n", sn.SnippetLicenseConcluded) + } + for _, s := range sn.LicenseInfoInSnippet { + fmt.Fprintf(w, "LicenseInfoInSnippet: %s\n", s) + } + if sn.SnippetLicenseComments != "" { + fmt.Fprintf(w, "SnippetLicenseComments: %s\n", textify(sn.SnippetLicenseComments)) + } + if sn.SnippetCopyrightText != "" { + fmt.Fprintf(w, "SnippetCopyrightText: %s\n", textify(sn.SnippetCopyrightText)) + } + if sn.SnippetComment != "" { + fmt.Fprintf(w, "SnippetComment: %s\n", textify(sn.SnippetComment)) + } + if sn.SnippetName != "" { + fmt.Fprintf(w, "SnippetName: %s\n", sn.SnippetName) + } + for _, s := range sn.SnippetAttributionTexts { + fmt.Fprintf(w, "SnippetAttributionText: %s\n", textify(s)) + } + + fmt.Fprintf(w, "\n") + + return nil +} diff --git a/tvsaver/saver2v3/save_snippet_test.go b/tvsaver/saver2v3/save_snippet_test.go new file mode 100644 index 0000000..ef10194 --- /dev/null +++ b/tvsaver/saver2v3/save_snippet_test.go @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "bytes" + "testing" + + "github.com/spdx/tools-golang/spdx/common" + "github.com/spdx/tools-golang/spdx/v2_3" +) + +// ===== Snippet section Saver tests ===== +func TestSaver2_3SnippetSavesText(t *testing.T) { + sn := &v2_3.Snippet{ + SnippetSPDXIdentifier: common.ElementID("Snippet17"), + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "File292").ElementRefID, + Ranges: []common.SnippetRange{ + { + StartPointer: common.SnippetRangePointer{LineNumber: 3}, + EndPointer: common.SnippetRangePointer{LineNumber: 8}, + }, + { + StartPointer: common.SnippetRangePointer{Offset: 17}, + EndPointer: common.SnippetRangePointer{Offset: 209}, + }, + }, + SnippetLicenseConcluded: "GPL-2.0-or-later", + LicenseInfoInSnippet: []string{ + "GPL-2.0-or-later", + "MIT", + }, + SnippetLicenseComments: "this is a comment(s) about the snippet license", + SnippetCopyrightText: "Copyright (c) John Doe 20x6", + SnippetComment: "this is a snippet comment", + SnippetName: "from John's program", + SnippetAttributionTexts: []string{"some attributions"}, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`SnippetSPDXID: SPDXRef-Snippet17 +SnippetFromFileSPDXID: SPDXRef-File292 +SnippetLineRange: 3:8 +SnippetByteRange: 17:209 +SnippetLicenseConcluded: GPL-2.0-or-later +LicenseInfoInSnippet: GPL-2.0-or-later +LicenseInfoInSnippet: MIT +SnippetLicenseComments: this is a comment(s) about the snippet license +SnippetCopyrightText: Copyright (c) John Doe 20x6 +SnippetComment: this is a snippet comment +SnippetName: from John's program +SnippetAttributionText: some attributions + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderSnippet2_3(sn, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3SnippetOmitsOptionalFieldsIfEmpty(t *testing.T) { + sn := &v2_3.Snippet{ + SnippetSPDXIdentifier: common.ElementID("Snippet17"), + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "File292").ElementRefID, + Ranges: []common.SnippetRange{ + { + StartPointer: common.SnippetRangePointer{Offset: 17}, + EndPointer: common.SnippetRangePointer{Offset: 209}, + }, + }, + SnippetLicenseConcluded: "GPL-2.0-or-later", + SnippetCopyrightText: "Copyright (c) John Doe 20x6", + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`SnippetSPDXID: SPDXRef-Snippet17 +SnippetFromFileSPDXID: SPDXRef-File292 +SnippetByteRange: 17:209 +SnippetLicenseConcluded: GPL-2.0-or-later +SnippetCopyrightText: Copyright (c) John Doe 20x6 + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderSnippet2_3(sn, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} + +func TestSaver2_3SnippetWrapsCopyrightMultiline(t *testing.T) { + sn := &v2_3.Snippet{ + SnippetSPDXIdentifier: common.ElementID("Snippet17"), + SnippetFromFileSPDXIdentifier: common.MakeDocElementID("", "File292").ElementRefID, + Ranges: []common.SnippetRange{ + { + StartPointer: common.SnippetRangePointer{Offset: 17}, + EndPointer: common.SnippetRangePointer{Offset: 209}, + }, + }, + SnippetLicenseConcluded: "GPL-2.0-or-later", + SnippetCopyrightText: `Copyright (c) John Doe 20x6 +Copyright (c) John Doe 20x6`, + } + + // what we want to get, as a buffer of bytes + want := bytes.NewBufferString(`SnippetSPDXID: SPDXRef-Snippet17 +SnippetFromFileSPDXID: SPDXRef-File292 +SnippetByteRange: 17:209 +SnippetLicenseConcluded: GPL-2.0-or-later +SnippetCopyrightText: <text>Copyright (c) John Doe 20x6 +Copyright (c) John Doe 20x6</text> + +`) + + // render as buffer of bytes + var got bytes.Buffer + err := renderSnippet2_3(sn, &got) + if err != nil { + t.Errorf("Expected nil error, got %v", err) + } + + // check that they match + c := bytes.Compare(want.Bytes(), got.Bytes()) + if c != 0 { + t.Errorf("Expected %v, got %v", want.String(), got.String()) + } +} diff --git a/tvsaver/saver2v3/util.go b/tvsaver/saver2v3/util.go new file mode 100644 index 0000000..4dec724 --- /dev/null +++ b/tvsaver/saver2v3/util.go @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "fmt" + "strings" +) + +func textify(s string) string { + if strings.Contains(s, "\n") { + return fmt.Sprintf("<text>%s</text>", s) + } + + return s +} diff --git a/tvsaver/saver2v3/util_test.go b/tvsaver/saver2v3/util_test.go new file mode 100644 index 0000000..b4b7553 --- /dev/null +++ b/tvsaver/saver2v3/util_test.go @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later + +package saver2v3 + +import ( + "testing" +) + +// ===== Utility function tests ===== +func TestTextifyWrapsStringWithNewline(t *testing.T) { + s := `this text has +a newline in it` + want := `<text>this text has +a newline in it</text>` + + got := textify(s) + + if want != got { + t.Errorf("Expected %s, got %s", want, got) + } +} + +func TestTextifyDoesNotWrapsStringWithNoNewline(t *testing.T) { + s := `this text has no newline in it` + want := s + + got := textify(s) + + if want != got { + t.Errorf("Expected %s, got %s", want, got) + } +} |