aboutsummaryrefslogtreecommitdiff
path: root/tvloader/parser2v3
diff options
context:
space:
mode:
Diffstat (limited to 'tvloader/parser2v3')
-rw-r--r--tvloader/parser2v3/parse_annotation.go43
-rw-r--r--tvloader/parser2v3/parse_annotation_test.go158
-rw-r--r--tvloader/parser2v3/parse_creation_info.go156
-rw-r--r--tvloader/parser2v3/parse_creation_info_test.go427
-rw-r--r--tvloader/parser2v3/parse_file.go151
-rw-r--r--tvloader/parser2v3/parse_file_test.go965
-rw-r--r--tvloader/parser2v3/parse_other_license.go55
-rw-r--r--tvloader/parser2v3/parse_other_license_test.go339
-rw-r--r--tvloader/parser2v3/parse_package.go232
-rw-r--r--tvloader/parser2v3/parse_package_test.go1130
-rw-r--r--tvloader/parser2v3/parse_relationship.go54
-rw-r--r--tvloader/parser2v3/parse_relationship_test.go202
-rw-r--r--tvloader/parser2v3/parse_review.go63
-rw-r--r--tvloader/parser2v3/parse_review_test.go414
-rw-r--r--tvloader/parser2v3/parse_snippet.go137
-rw-r--r--tvloader/parser2v3/parse_snippet_test.go635
-rw-r--r--tvloader/parser2v3/parser.go100
-rw-r--r--tvloader/parser2v3/parser_test.go97
-rw-r--r--tvloader/parser2v3/types.go57
-rw-r--r--tvloader/parser2v3/util.go115
-rw-r--r--tvloader/parser2v3/util_test.go156
21 files changed, 5686 insertions, 0 deletions
diff --git a/tvloader/parser2v3/parse_annotation.go b/tvloader/parser2v3/parse_annotation.go
new file mode 100644
index 0000000..37861ba
--- /dev/null
+++ b/tvloader/parser2v3/parse_annotation.go
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+)
+
+func (parser *tvParser2_3) parsePairForAnnotation2_3(tag string, value string) error {
+ if parser.ann == nil {
+ return fmt.Errorf("no annotation struct created in parser ann pointer")
+ }
+
+ switch tag {
+ case "Annotator":
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ if subkey == "Person" || subkey == "Organization" || subkey == "Tool" {
+ parser.ann.Annotator.AnnotatorType = subkey
+ parser.ann.Annotator.Annotator = subvalue
+ return nil
+ }
+ return fmt.Errorf("unrecognized Annotator type %v", subkey)
+ case "AnnotationDate":
+ parser.ann.AnnotationDate = value
+ case "AnnotationType":
+ parser.ann.AnnotationType = value
+ case "SPDXREF":
+ deID, err := extractDocElementID(value)
+ if err != nil {
+ return err
+ }
+ parser.ann.AnnotationSPDXIdentifier = deID
+ case "AnnotationComment":
+ parser.ann.AnnotationComment = value
+ default:
+ return fmt.Errorf("received unknown tag %v in Annotation section", tag)
+ }
+
+ return nil
+}
diff --git a/tvloader/parser2v3/parse_annotation_test.go b/tvloader/parser2v3/parse_annotation_test.go
new file mode 100644
index 0000000..6681ed5
--- /dev/null
+++ b/tvloader/parser2v3/parse_annotation_test.go
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Annotation section tests =====
+func TestParser2_3FailsIfAnnotationNotSet(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePairForAnnotation2_3("Annotator", "Person: John Doe (jdoe@example.com)")
+ if err == nil {
+ t.Errorf("expected error when calling parsePairFromAnnotation2_3 without setting ann pointer")
+ }
+}
+
+func TestParser2_3FailsIfAnnotationTagUnknown(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ // start with valid annotator
+ err := parser.parsePair2_3("Annotator", "Person: John Doe (jdoe@example.com)")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // parse invalid tag, using parsePairForAnnotation2_3(
+ err = parser.parsePairForAnnotation2_3("blah", "oops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfAnnotationFieldsWithoutAnnotation(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("AnnotationDate", "2018-09-15T17:25:00Z")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3 for AnnotationDate without Annotator first")
+ }
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3 for AnnotationType without Annotator first")
+ }
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3 for SPDXREF without Annotator first")
+ }
+ err = parser.parsePair2_3("AnnotationComment", "comment whatever")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3 for AnnotationComment without Annotator first")
+ }
+}
+
+func TestParser2_3CanParseAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // Annotator without email address
+ err := parser.parsePair2_3("Annotator", "Person: John Doe")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.ann.Annotator.Annotator != "John Doe" {
+ t.Errorf("got %+v for Annotator, expected John Doe", parser.ann.Annotator.Annotator)
+ }
+ if parser.ann.Annotator.AnnotatorType != "Person" {
+ t.Errorf("got %v for AnnotatorType, expected Person", parser.ann.Annotator.AnnotatorType)
+ }
+
+ // Annotation Date
+ dt := "2018-09-15T17:32:00Z"
+ err = parser.parsePair2_3("AnnotationDate", dt)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.ann.AnnotationDate != dt {
+ t.Errorf("got %v for AnnotationDate, expected %v", parser.ann.AnnotationDate, dt)
+ }
+
+ // Annotation type
+ aType := "REVIEW"
+ err = parser.parsePair2_3("AnnotationType", aType)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.ann.AnnotationType != aType {
+ t.Errorf("got %v for AnnotationType, expected %v", parser.ann.AnnotationType, aType)
+ }
+
+ // SPDX Identifier Reference
+ ref := "SPDXRef-30"
+ err = parser.parsePair2_3("SPDXREF", ref)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ deID := parser.ann.AnnotationSPDXIdentifier
+ if deID.DocumentRefID != "" || deID.ElementRefID != "30" {
+ t.Errorf("got %v for SPDXREF, expected %v", parser.ann.AnnotationSPDXIdentifier, "30")
+ }
+
+ // Annotation Comment
+ cmt := "this is a comment"
+ err = parser.parsePair2_3("AnnotationComment", cmt)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.ann.AnnotationComment != cmt {
+ t.Errorf("got %v for AnnotationComment, expected %v", parser.ann.AnnotationComment, cmt)
+ }
+}
+
+func TestParser2_3FailsIfAnnotatorInvalid(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("Annotator", "John Doe (jdoe@example.com)")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfAnnotatorTypeInvalid(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("Annotator", "Human: John Doe (jdoe@example.com)")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfAnnotationRefInvalid(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ // start with valid annotator
+ err := parser.parsePair2_3("Annotator", "Person: John Doe (jdoe@example.com)")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePair2_3("SPDXREF", "blah:other")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
diff --git a/tvloader/parser2v3/parse_creation_info.go b/tvloader/parser2v3/parse_creation_info.go
new file mode 100644
index 0000000..693a56f
--- /dev/null
+++ b/tvloader/parser2v3/parse_creation_info.go
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+func (parser *tvParser2_3) parsePairFromCreationInfo2_3(tag string, value string) error {
+ // fail if not in Creation Info parser state
+ if parser.st != psCreationInfo2_3 {
+ return fmt.Errorf("got invalid state %v in parsePairFromCreationInfo2_3", parser.st)
+ }
+
+ // create an SPDX Creation Info data struct if we don't have one already
+ if parser.doc.CreationInfo == nil {
+ parser.doc.CreationInfo = &v2_3.CreationInfo{}
+ }
+
+ ci := parser.doc.CreationInfo
+ switch tag {
+ case "LicenseListVersion":
+ ci.LicenseListVersion = value
+ case "Creator":
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+
+ creator := common.Creator{Creator: subvalue}
+ switch subkey {
+ case "Person", "Organization", "Tool":
+ creator.CreatorType = subkey
+ default:
+ return fmt.Errorf("unrecognized Creator type %v", subkey)
+ }
+
+ ci.Creators = append(ci.Creators, creator)
+ case "Created":
+ ci.Created = value
+ case "CreatorComment":
+ ci.CreatorComment = value
+
+ // tag for going on to package section
+ case "PackageName":
+ // error if last file does not have an identifier
+ // this may be a null case: can we ever have a "last file" in
+ // the "creation info" state? should go on to "file" state
+ // even when parsing unpackaged files.
+ if parser.file != nil && parser.file.FileSPDXIdentifier == nullSpdxElementId2_3 {
+ return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName)
+ }
+ parser.st = psPackage2_3
+ parser.pkg = &v2_3.Package{
+ FilesAnalyzed: true,
+ IsFilesAnalyzedTagPresent: false,
+ }
+ return parser.parsePairFromPackage2_3(tag, value)
+ // tag for going on to _unpackaged_ file section
+ case "FileName":
+ // leave pkg as nil, so that packages will be placed in Files
+ parser.st = psFile2_3
+ parser.pkg = nil
+ return parser.parsePairFromFile2_3(tag, value)
+ // tag for going on to other license section
+ case "LicenseID":
+ parser.st = psOtherLicense2_3
+ return parser.parsePairFromOtherLicense2_3(tag, value)
+ // tag for going on to review section (DEPRECATED)
+ case "Reviewer":
+ parser.st = psReview2_3
+ return parser.parsePairFromReview2_3(tag, value)
+ // for relationship tags, pass along but don't change state
+ case "Relationship":
+ parser.rln = &v2_3.Relationship{}
+ parser.doc.Relationships = append(parser.doc.Relationships, parser.rln)
+ return parser.parsePairForRelationship2_3(tag, value)
+ case "RelationshipComment":
+ return parser.parsePairForRelationship2_3(tag, value)
+ // for annotation tags, pass along but don't change state
+ case "Annotator":
+ parser.ann = &v2_3.Annotation{}
+ parser.doc.Annotations = append(parser.doc.Annotations, parser.ann)
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationDate":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationType":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "SPDXREF":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationComment":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ default:
+ return fmt.Errorf("received unknown tag %v in CreationInfo section", tag)
+ }
+
+ return nil
+}
+
+// ===== Helper functions =====
+
+func extractExternalDocumentReference(value string) (string, string, string, string, error) {
+ sp := strings.Split(value, " ")
+ // remove any that are just whitespace
+ keepSp := []string{}
+ for _, s := range sp {
+ ss := strings.TrimSpace(s)
+ if ss != "" {
+ keepSp = append(keepSp, ss)
+ }
+ }
+
+ var documentRefID, uri, alg, checksum string
+
+ // now, should have 4 items (or 3, if Alg and Checksum were joined)
+ // and should be able to map them
+ if len(keepSp) == 4 {
+ documentRefID = keepSp[0]
+ uri = keepSp[1]
+ alg = keepSp[2]
+ // check that colon is present for alg, and remove it
+ if !strings.HasSuffix(alg, ":") {
+ return "", "", "", "", fmt.Errorf("algorithm does not end with colon")
+ }
+ alg = strings.TrimSuffix(alg, ":")
+ checksum = keepSp[3]
+ } else if len(keepSp) == 3 {
+ documentRefID = keepSp[0]
+ uri = keepSp[1]
+ // split on colon into alg and checksum
+ parts := strings.SplitN(keepSp[2], ":", 2)
+ if len(parts) != 2 {
+ return "", "", "", "", fmt.Errorf("missing colon separator between algorithm and checksum")
+ }
+ alg = parts[0]
+ checksum = parts[1]
+ } else {
+ return "", "", "", "", fmt.Errorf("expected 4 elements, got %d", len(keepSp))
+ }
+
+ // additionally, we should be able to parse the first element as a
+ // DocumentRef- ID string, and we should remove that prefix
+ if !strings.HasPrefix(documentRefID, "DocumentRef-") {
+ return "", "", "", "", fmt.Errorf("expected first element to have DocumentRef- prefix")
+ }
+ documentRefID = strings.TrimPrefix(documentRefID, "DocumentRef-")
+ if documentRefID == "" {
+ return "", "", "", "", fmt.Errorf("document identifier has nothing after prefix")
+ }
+
+ return documentRefID, uri, alg, checksum, nil
+}
diff --git a/tvloader/parser2v3/parse_creation_info_test.go b/tvloader/parser2v3/parse_creation_info_test.go
new file mode 100644
index 0000000..24cb80a
--- /dev/null
+++ b/tvloader/parser2v3/parse_creation_info_test.go
@@ -0,0 +1,427 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Parser creation info state change tests =====
+func TestParser2_3CIMovesToPackageAfterParsingPackageNameTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ pkgName := "testPkg"
+ err := parser.parsePair2_3("PackageName", pkgName)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psPackage2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psPackage2_3)
+ }
+ // and a package should be created
+ if parser.pkg == nil {
+ t.Fatalf("parser didn't create new package")
+ }
+ // and the package name should be as expected
+ if parser.pkg.PackageName != pkgName {
+ t.Errorf("expected package name %s, got %s", pkgName, parser.pkg.PackageName)
+ }
+ // and the package should default to true for FilesAnalyzed
+ if parser.pkg.FilesAnalyzed != true {
+ t.Errorf("expected FilesAnalyzed to default to true, got false")
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != false {
+ t.Errorf("expected IsFilesAnalyzedTagPresent to default to false, got true")
+ }
+ // and the package should NOT be in the SPDX Document's map of packages,
+ // because it doesn't have an SPDX identifier yet
+ if len(parser.doc.Packages) != 0 {
+ t.Errorf("expected 0 packages, got %d", len(parser.doc.Packages))
+ }
+}
+
+func TestParser2_3CIMovesToFileAfterParsingFileNameTagWithNoPackages(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("FileName", "testFile")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psFile2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psFile2_3)
+ }
+ // and current package should be nil, meaning Files are placed in the
+ // Files map instead of in a Package
+ if parser.pkg != nil {
+ t.Fatalf("expected pkg to be nil, got non-nil pkg")
+ }
+}
+
+func TestParser2_3CIMovesToOtherLicenseAfterParsingLicenseIDTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("LicenseID", "LicenseRef-TestLic")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psOtherLicense2_3)
+ }
+}
+
+func TestParser2_3CIMovesToReviewAfterParsingReviewerTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("Reviewer", "Person: John Doe")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psReview2_3)
+ }
+}
+
+func TestParser2_3CIStaysAfterParsingRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-else")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+
+ err = parser.parsePair2_3("RelationshipComment", "blah")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+}
+
+func TestParser2_3CIStaysAfterParsingAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationDate", "2018-09-15T00:36:00Z")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationComment", "i guess i had something to say about this spdx file")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psCreationInfo2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psCreationInfo2_3)
+ }
+}
+
+func TestParser2_3FailsParsingCreationInfoWithInvalidState(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psPackage2_3,
+ }
+ err := parser.parsePairFromCreationInfo2_3("SPDXVersion", "SPDX-2.3")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+// ===== Creation Info section tests =====
+func TestParser2_3HasCreationInfoAfterCallToParseFirstTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePairFromCreationInfo2_3("LicenseListVersion", "3.9")
+ if err != nil {
+ t.Errorf("got error when calling parsePairFromCreationInfo2_3: %v", err)
+ }
+ if parser.doc.CreationInfo == nil {
+ t.Errorf("doc.CreationInfo is still nil after parsing first pair")
+ }
+}
+
+func TestParser2_3CanParseCreationInfoTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // License List Version
+ err := parser.parsePairFromCreationInfo2_3("LicenseListVersion", "2.3")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.doc.CreationInfo.LicenseListVersion != "2.3" {
+ t.Errorf("got %v for LicenseListVersion", parser.doc.CreationInfo.LicenseListVersion)
+ }
+
+ // Creators: Persons
+ refPersons := []string{
+ "Person: Person A",
+ "Person: Person B",
+ }
+ err = parser.parsePairFromCreationInfo2_3("Creator", refPersons[0])
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromCreationInfo2_3("Creator", refPersons[1])
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if len(parser.doc.CreationInfo.Creators) != 2 ||
+ parser.doc.CreationInfo.Creators[0].Creator != "Person A" ||
+ parser.doc.CreationInfo.Creators[1].Creator != "Person B" {
+ t.Errorf("got %v for CreatorPersons", parser.doc.CreationInfo.Creators)
+ }
+
+ // Creators: Organizations
+ refOrgs := []string{
+ "Organization: Organization A",
+ "Organization: Organization B",
+ }
+ err = parser.parsePairFromCreationInfo2_3("Creator", refOrgs[0])
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromCreationInfo2_3("Creator", refOrgs[1])
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if len(parser.doc.CreationInfo.Creators) != 4 ||
+ parser.doc.CreationInfo.Creators[2].Creator != "Organization A" ||
+ parser.doc.CreationInfo.Creators[3].Creator != "Organization B" {
+ t.Errorf("got %v for CreatorOrganizations", parser.doc.CreationInfo.Creators)
+ }
+
+ // Creators: Tools
+ refTools := []string{
+ "Tool: Tool A",
+ "Tool: Tool B",
+ }
+ err = parser.parsePairFromCreationInfo2_3("Creator", refTools[0])
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromCreationInfo2_3("Creator", refTools[1])
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if len(parser.doc.CreationInfo.Creators) != 6 ||
+ parser.doc.CreationInfo.Creators[4].Creator != "Tool A" ||
+ parser.doc.CreationInfo.Creators[5].Creator != "Tool B" {
+ t.Errorf("got %v for CreatorTools", parser.doc.CreationInfo.Creators)
+ }
+
+ // Created date
+ err = parser.parsePairFromCreationInfo2_3("Created", "2018-09-10T11:46:00Z")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.doc.CreationInfo.Created != "2018-09-10T11:46:00Z" {
+ t.Errorf("got %v for Created", parser.doc.CreationInfo.Created)
+ }
+
+ // Creator Comment
+ err = parser.parsePairFromCreationInfo2_3("CreatorComment", "Blah whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.doc.CreationInfo.CreatorComment != "Blah whatever" {
+ t.Errorf("got %v for CreatorComment", parser.doc.CreationInfo.CreatorComment)
+ }
+}
+
+func TestParser2_3InvalidCreatorTagsFail(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePairFromCreationInfo2_3("Creator", "blah: somebody")
+ if err == nil {
+ t.Errorf("expected error from parsing invalid Creator format, got nil")
+ }
+
+ err = parser.parsePairFromCreationInfo2_3("Creator", "Tool with no colons")
+ if err == nil {
+ t.Errorf("expected error from parsing invalid Creator format, got nil")
+ }
+}
+
+func TestParser2_3CreatorTagWithMultipleColonsPasses(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePairFromCreationInfo2_3("Creator", "Tool: tool1:2:3")
+ if err != nil {
+ t.Errorf("unexpected error from parsing valid Creator format")
+ }
+}
+
+func TestParser2_3CIUnknownTagFails(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePairFromCreationInfo2_3("blah", "something")
+ if err == nil {
+ t.Errorf("expected error from parsing unknown tag")
+ }
+}
+
+func TestParser2_3CICreatesRelationship(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-whatever")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.rln == nil {
+ t.Fatalf("parser didn't create and point to Relationship struct")
+ }
+ if parser.rln != parser.doc.Relationships[0] {
+ t.Errorf("pointer to new Relationship doesn't match idx 0 for doc.Relationships[]")
+ }
+}
+
+func TestParser2_3CICreatesAnnotation(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.ann == nil {
+ t.Fatalf("parser didn't create and point to Annotation struct")
+ }
+ if parser.ann != parser.doc.Annotations[0] {
+ t.Errorf("pointer to new Annotation doesn't match idx 0 for doc.Annotations[]")
+ }
+}
+
+// ===== Helper function tests =====
+
+func TestCanExtractExternalDocumentReference(t *testing.T) {
+ refstring := "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759"
+ wantDocumentRefID := "spdx-tool-1.2"
+ wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301"
+ wantAlg := "SHA1"
+ wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759"
+
+ gotDocumentRefID, gotURI, gotAlg, gotChecksum, err := extractExternalDocumentReference(refstring)
+ if err != nil {
+ t.Errorf("got non-nil error: %v", err)
+ }
+ if wantDocumentRefID != gotDocumentRefID {
+ t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, gotDocumentRefID)
+ }
+ if wantURI != gotURI {
+ t.Errorf("wanted URI %s, got %s", wantURI, gotURI)
+ }
+ if wantAlg != gotAlg {
+ t.Errorf("wanted alg %s, got %s", wantAlg, gotAlg)
+ }
+ if wantChecksum != gotChecksum {
+ t.Errorf("wanted checksum %s, got %s", wantChecksum, gotChecksum)
+ }
+}
+
+func TestCanExtractExternalDocumentReferenceWithExtraWhitespace(t *testing.T) {
+ refstring := " DocumentRef-spdx-tool-1.2 \t http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 \t SHA1: \t d6a770ba38583ed4bb4525bd96e50461655d2759"
+ wantDocumentRefID := "spdx-tool-1.2"
+ wantURI := "http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301"
+ wantAlg := "SHA1"
+ wantChecksum := "d6a770ba38583ed4bb4525bd96e50461655d2759"
+
+ gotDocumentRefID, gotURI, gotAlg, gotChecksum, err := extractExternalDocumentReference(refstring)
+ if err != nil {
+ t.Errorf("got non-nil error: %v", err)
+ }
+ if wantDocumentRefID != gotDocumentRefID {
+ t.Errorf("wanted document ref ID %s, got %s", wantDocumentRefID, gotDocumentRefID)
+ }
+ if wantURI != gotURI {
+ t.Errorf("wanted URI %s, got %s", wantURI, gotURI)
+ }
+ if wantAlg != gotAlg {
+ t.Errorf("wanted alg %s, got %s", wantAlg, gotAlg)
+ }
+ if wantChecksum != gotChecksum {
+ t.Errorf("wanted checksum %s, got %s", wantChecksum, gotChecksum)
+ }
+}
+
+func TestFailsExternalDocumentReferenceWithInvalidFormats(t *testing.T) {
+ invalidRefs := []string{
+ "whoops",
+ "DocumentRef-",
+ "DocumentRef- ",
+ "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301",
+ "DocumentRef-spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 d6a770ba38583ed4bb4525bd96e50461655d2759",
+ "DocumentRef-spdx-tool-1.2",
+ "spdx-tool-1.2 http://spdx.org/spdxdocs/spdx-tools-v1.2-3F2504E0-4F89-41D3-9A0C-0305E82C3301 SHA1:d6a770ba38583ed4bb4525bd96e50461655d2759",
+ }
+ for _, refstring := range invalidRefs {
+ _, _, _, _, err := extractExternalDocumentReference(refstring)
+ if err == nil {
+ t.Errorf("expected non-nil error for %s, got nil", refstring)
+ }
+ }
+}
diff --git a/tvloader/parser2v3/parse_file.go b/tvloader/parser2v3/parse_file.go
new file mode 100644
index 0000000..7f6d99c
--- /dev/null
+++ b/tvloader/parser2v3/parse_file.go
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+func (parser *tvParser2_3) parsePairFromFile2_3(tag string, value string) error {
+ // expire fileAOP for anything other than an AOPHomePage or AOPURI
+ // (we'll actually handle the HomePage and URI further below)
+ if tag != "ArtifactOfProjectHomePage" && tag != "ArtifactOfProjectURI" {
+ parser.fileAOP = nil
+ }
+
+ switch tag {
+ // tag for creating new file section
+ case "FileName":
+ // check if the previous file contained an spdx Id or not
+ if parser.file != nil && parser.file.FileSPDXIdentifier == nullSpdxElementId2_3 {
+ return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName)
+ }
+ parser.file = &v2_3.File{}
+ parser.file.FileName = value
+ // tag for creating new package section and going back to parsing Package
+ case "PackageName":
+ parser.st = psPackage2_3
+ // check if the previous file contained an spdx Id or not
+ if parser.file != nil && parser.file.FileSPDXIdentifier == nullSpdxElementId2_3 {
+ return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName)
+ }
+ parser.file = nil
+ return parser.parsePairFromPackage2_3(tag, value)
+ // tag for going on to snippet section
+ case "SnippetSPDXID":
+ parser.st = psSnippet2_3
+ return parser.parsePairFromSnippet2_3(tag, value)
+ // tag for going on to other license section
+ case "LicenseID":
+ parser.st = psOtherLicense2_3
+ return parser.parsePairFromOtherLicense2_3(tag, value)
+ // tags for file data
+ case "SPDXID":
+ eID, err := extractElementID(value)
+ if err != nil {
+ return err
+ }
+ parser.file.FileSPDXIdentifier = eID
+ if parser.pkg == nil {
+ if parser.doc.Files == nil {
+ parser.doc.Files = []*v2_3.File{}
+ }
+ parser.doc.Files = append(parser.doc.Files, parser.file)
+ } else {
+ if parser.pkg.Files == nil {
+ parser.pkg.Files = []*v2_3.File{}
+ }
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ }
+ case "FileType":
+ parser.file.FileTypes = append(parser.file.FileTypes, value)
+ case "FileChecksum":
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ if parser.file.Checksums == nil {
+ parser.file.Checksums = []common.Checksum{}
+ }
+ switch common.ChecksumAlgorithm(subkey) {
+ case common.SHA1,
+ common.SHA256,
+ common.MD5,
+ common.SHA3_256,
+ common.SHA3_384,
+ common.SHA3_512,
+ common.BLAKE2b_256,
+ common.BLAKE2b_384,
+ common.BLAKE2b_512,
+ common.BLAKE3,
+ common.ADLER32:
+ algorithm := common.ChecksumAlgorithm(subkey)
+ parser.file.Checksums = append(parser.file.Checksums, common.Checksum{Algorithm: algorithm, Value: subvalue})
+ default:
+ return fmt.Errorf("got unknown checksum type %s", subkey)
+ }
+ case "LicenseConcluded":
+ parser.file.LicenseConcluded = value
+ case "LicenseInfoInFile":
+ parser.file.LicenseInfoInFiles = append(parser.file.LicenseInfoInFiles, value)
+ case "LicenseComments":
+ parser.file.LicenseComments = value
+ case "FileCopyrightText":
+ parser.file.FileCopyrightText = value
+ case "ArtifactOfProjectName":
+ parser.fileAOP = &v2_3.ArtifactOfProject{}
+ parser.file.ArtifactOfProjects = append(parser.file.ArtifactOfProjects, parser.fileAOP)
+ parser.fileAOP.Name = value
+ case "ArtifactOfProjectHomePage":
+ if parser.fileAOP == nil {
+ return fmt.Errorf("no current ArtifactOfProject found")
+ }
+ parser.fileAOP.HomePage = value
+ case "ArtifactOfProjectURI":
+ if parser.fileAOP == nil {
+ return fmt.Errorf("no current ArtifactOfProject found")
+ }
+ parser.fileAOP.URI = value
+ case "FileComment":
+ parser.file.FileComment = value
+ case "FileNotice":
+ parser.file.FileNotice = value
+ case "FileContributor":
+ parser.file.FileContributors = append(parser.file.FileContributors, value)
+ case "FileDependency":
+ parser.file.FileDependencies = append(parser.file.FileDependencies, value)
+ case "FileAttributionText":
+ parser.file.FileAttributionTexts = append(parser.file.FileAttributionTexts, value)
+ // for relationship tags, pass along but don't change state
+ case "Relationship":
+ parser.rln = &v2_3.Relationship{}
+ parser.doc.Relationships = append(parser.doc.Relationships, parser.rln)
+ return parser.parsePairForRelationship2_3(tag, value)
+ case "RelationshipComment":
+ return parser.parsePairForRelationship2_3(tag, value)
+ // for annotation tags, pass along but don't change state
+ case "Annotator":
+ parser.ann = &v2_3.Annotation{}
+ parser.doc.Annotations = append(parser.doc.Annotations, parser.ann)
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationDate":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationType":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "SPDXREF":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationComment":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ // tag for going on to review section (DEPRECATED)
+ case "Reviewer":
+ parser.st = psReview2_3
+ return parser.parsePairFromReview2_3(tag, value)
+ default:
+ return fmt.Errorf("received unknown tag %v in File section", tag)
+ }
+
+ return nil
+}
diff --git a/tvloader/parser2v3/parse_file_test.go b/tvloader/parser2v3/parse_file_test.go
new file mode 100644
index 0000000..c72aa86
--- /dev/null
+++ b/tvloader/parser2v3/parse_file_test.go
@@ -0,0 +1,965 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Parser file section state change tests =====
+func TestParser2_3FileStartsNewFileAfterParsingFileNameTag(t *testing.T) {
+ // create the first file
+ fileOldName := "f1.txt"
+
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: fileOldName, FileSPDXIdentifier: "f1"},
+ }
+ fileOld := parser.file
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, fileOld)
+ // the Package's Files should have this one only
+ if len(parser.pkg.Files) != 1 {
+ t.Fatalf("expected 1 file, got %d", len(parser.pkg.Files))
+ }
+ if parser.pkg.Files[0] != fileOld {
+ t.Errorf("expected file %v in Files[f1], got %v", fileOld, parser.pkg.Files[0])
+ }
+ if parser.pkg.Files[0].FileName != fileOldName {
+ t.Errorf("expected file name %s in Files[f1], got %s", fileOldName, parser.pkg.Files[0].FileName)
+ }
+
+ // now add a new file
+ fileName := "f2.txt"
+ err := parser.parsePair2_3("FileName", fileName)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psFile2_3 {
+ t.Errorf("expected state to be %v, got %v", psFile2_3, parser.st)
+ }
+ // and a file should be created
+ if parser.file == nil {
+ t.Fatalf("parser didn't create new file")
+ }
+ // and the file name should be as expected
+ if parser.file.FileName != fileName {
+ t.Errorf("expected file name %s, got %s", fileName, parser.file.FileName)
+ }
+ // and the Package's Files should still be of size 1 and not have this new
+ // one yet, since it hasn't seen an SPDX identifier
+ if len(parser.pkg.Files) != 1 {
+ t.Fatalf("expected 1 file, got %d", len(parser.pkg.Files))
+ }
+ if parser.pkg.Files[0] != fileOld {
+ t.Errorf("expected file %v in Files[f1], got %v", fileOld, parser.pkg.Files[0])
+ }
+ if parser.pkg.Files[0].FileName != fileOldName {
+ t.Errorf("expected file name %s in Files[f1], got %s", fileOldName, parser.pkg.Files[0].FileName)
+ }
+
+ // now parse an SPDX identifier tag
+ err = parser.parsePair2_3("SPDXID", "SPDXRef-f2ID")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // and the Package's Files should now be of size 2 and have this new one
+ if len(parser.pkg.Files) != 2 {
+ t.Fatalf("expected 2 files, got %d", len(parser.pkg.Files))
+ }
+ if parser.pkg.Files[0] != fileOld {
+ t.Errorf("expected file %v in Files[f1], got %v", fileOld, parser.pkg.Files[0])
+ }
+ if parser.pkg.Files[0].FileName != fileOldName {
+ t.Errorf("expected file name %s in Files[f1], got %s", fileOldName, parser.pkg.Files[0].FileName)
+ }
+ if parser.pkg.Files[1] != parser.file {
+ t.Errorf("expected file %v in Files[f2ID], got %v", parser.file, parser.pkg.Files[1])
+ }
+ if parser.pkg.Files[1].FileName != fileName {
+ t.Errorf("expected file name %s in Files[f2ID], got %s", fileName, parser.pkg.Files[1].FileName)
+ }
+}
+
+func TestParser2_3FileAddsToPackageOrUnpackagedFiles(t *testing.T) {
+ // start with no packages
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // add a file and SPDX identifier
+ fileName := "f2.txt"
+ err := parser.parsePair2_3("FileName", fileName)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ err = parser.parsePair2_3("SPDXID", "SPDXRef-f2ID")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ fileOld := parser.file
+ // should have been added to Files
+ if len(parser.doc.Files) != 1 {
+ t.Fatalf("expected 1 file in Files, got %d", len(parser.doc.Files))
+ }
+ if parser.doc.Files[0] != fileOld {
+ t.Errorf("expected file %v in Files[f2ID], got %v", fileOld, parser.doc.Files[0])
+ }
+ // now create a package and a new file
+ err = parser.parsePair2_3("PackageName", "package1")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ err = parser.parsePair2_3("SPDXID", "SPDXRef-pkg1")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ err = parser.parsePair2_3("FileName", "f3.txt")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ err = parser.parsePair2_3("SPDXID", "SPDXRef-f3ID")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // Files should still be size 1 and have old file only
+ if len(parser.doc.Files) != 1 {
+ t.Fatalf("expected 1 file in Files, got %d", len(parser.doc.Files))
+ }
+ if parser.doc.Files[0] != fileOld {
+ t.Errorf("expected file %v in Files[f2ID], got %v", fileOld, parser.doc.Files[0])
+ }
+ // and new package should have gotten the new file
+ if len(parser.pkg.Files) != 1 {
+ t.Fatalf("expected 1 file in Files, got %d", len(parser.pkg.Files))
+ }
+ if parser.pkg.Files[0] != parser.file {
+ t.Errorf("expected file %v in Files[f3ID], got %v", parser.file, parser.pkg.Files[0])
+ }
+}
+
+func TestParser2_3FileStartsNewPackageAfterParsingPackageNameTag(t *testing.T) {
+ // create the first file and package
+ p1Name := "package1"
+ f1Name := "f1.txt"
+
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: p1Name, PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: f1Name, FileSPDXIdentifier: "f1"},
+ }
+ p1 := parser.pkg
+ f1 := parser.file
+ parser.doc.Packages = append(parser.doc.Packages, p1)
+ parser.pkg.Files = append(parser.pkg.Files, f1)
+
+ // now add a new package
+ p2Name := "package2"
+ err := parser.parsePair2_3("PackageName", p2Name)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should go back to Package
+ if parser.st != psPackage2_3 {
+ t.Errorf("expected state to be %v, got %v", psPackage2_3, parser.st)
+ }
+ // and a package should be created
+ if parser.pkg == nil {
+ t.Fatalf("parser didn't create new pkg")
+ }
+ // and the package name should be as expected
+ if parser.pkg.PackageName != p2Name {
+ t.Errorf("expected package name %s, got %s", p2Name, parser.pkg.PackageName)
+ }
+ // and the package should default to true for FilesAnalyzed
+ if parser.pkg.FilesAnalyzed != true {
+ t.Errorf("expected FilesAnalyzed to default to true, got false")
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != false {
+ t.Errorf("expected IsFilesAnalyzedTagPresent to default to false, got true")
+ }
+ // and the new Package should have no files
+ if len(parser.pkg.Files) != 0 {
+ t.Errorf("Expected no files in pkg.Files, got %d", len(parser.pkg.Files))
+ }
+ // and the Document's Packages should still be of size 1 and not have this
+ // new one, because no SPDX identifier has been seen yet
+ if len(parser.doc.Packages) != 1 {
+ t.Fatalf("expected 1 package, got %d", len(parser.doc.Packages))
+ }
+ if parser.doc.Packages[0] != p1 {
+ t.Errorf("Expected package %v in Packages[package1], got %v", p1, parser.doc.Packages[0])
+ }
+ if parser.doc.Packages[0].PackageName != p1Name {
+ t.Errorf("expected package name %s in Packages[package1], got %s", p1Name, parser.doc.Packages[0].PackageName)
+ }
+ // and the first Package's Files should be of size 1 and have f1 only
+ if len(parser.doc.Packages[0].Files) != 1 {
+ t.Errorf("Expected 1 file in Packages[package1].Files, got %d", len(parser.doc.Packages[0].Files))
+ }
+ if parser.doc.Packages[0].Files[0] != f1 {
+ t.Errorf("Expected file %v in Files[f1], got %v", f1, parser.doc.Packages[0].Files[0])
+ }
+ if parser.doc.Packages[0].Files[0].FileName != f1Name {
+ t.Errorf("expected file name %s in Files[f1], got %s", f1Name, parser.doc.Packages[0].Files[0].FileName)
+ }
+ // and the current file should be nil
+ if parser.file != nil {
+ t.Errorf("Expected nil for parser.file, got %v", parser.file)
+ }
+}
+
+func TestParser2_3FileMovesToSnippetAfterParsingSnippetSPDXIDTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ fileCurrent := parser.file
+
+ err := parser.parsePair2_3("SnippetSPDXID", "SPDXRef-Test1")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psSnippet2_3 {
+ t.Errorf("expected state to be %v, got %v", psSnippet2_3, parser.st)
+ }
+ // and current file should remain what it was
+ if parser.file != fileCurrent {
+ t.Fatalf("expected file to remain %v, got %v", fileCurrent, parser.file)
+ }
+}
+
+func TestParser2_3FileMovesToOtherLicenseAfterParsingLicenseIDTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePair2_3("LicenseID", "LicenseRef-TestLic")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+}
+
+func TestParser2_3FileMovesToReviewAfterParsingReviewerTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePair2_3("Reviewer", "Person: John Doe")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+}
+
+func TestParser2_3FileStaysAfterParsingRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-else")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should remain unchanged
+ if parser.st != psFile2_3 {
+ t.Errorf("expected state to be %v, got %v", psFile2_3, parser.st)
+ }
+
+ err = parser.parsePair2_3("RelationshipComment", "blah")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should still remain unchanged
+ if parser.st != psFile2_3 {
+ t.Errorf("expected state to be %v, got %v", psFile2_3, parser.st)
+ }
+}
+
+func TestParser2_3FileStaysAfterParsingAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psFile2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psFile2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationDate", "2018-09-15T00:36:00Z")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psFile2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psFile2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psFile2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psFile2_3)
+ }
+
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psFile2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psFile2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationComment", "i guess i had something to say about this particular file")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psFile2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psFile2_3)
+ }
+}
+
+// ===== File data section tests =====
+func TestParser2_3CanParseFileTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // File Name
+ err := parser.parsePairFromFile2_3("FileName", "f1.txt")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.FileName != "f1.txt" {
+ t.Errorf("got %v for FileName", parser.file.FileName)
+ }
+ // should not yet be added to the Packages file list, because we haven't
+ // seen an SPDX identifier yet
+ if len(parser.pkg.Files) != 0 {
+ t.Errorf("expected 0 files, got %d", len(parser.pkg.Files))
+ }
+
+ // File SPDX Identifier
+ err = parser.parsePairFromFile2_3("SPDXID", "SPDXRef-f1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.FileSPDXIdentifier != "f1" {
+ t.Errorf("got %v for FileSPDXIdentifier", parser.file.FileSPDXIdentifier)
+ }
+ // should now be added to the Packages file list
+ if len(parser.pkg.Files) != 1 {
+ t.Errorf("expected 1 file, got %d", len(parser.pkg.Files))
+ }
+ if parser.pkg.Files[0] != parser.file {
+ t.Errorf("expected Files[f1] to be %v, got %v", parser.file, parser.pkg.Files[0])
+ }
+
+ // File Type
+ fileTypes := []string{
+ "TEXT",
+ "DOCUMENTATION",
+ }
+ for _, ty := range fileTypes {
+ err = parser.parsePairFromFile2_3("FileType", ty)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, typeWant := range fileTypes {
+ flagFound := false
+ for _, typeCheck := range parser.file.FileTypes {
+ if typeWant == typeCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in FileTypes", typeWant)
+ }
+ }
+ if len(fileTypes) != len(parser.file.FileTypes) {
+ t.Errorf("expected %d types in FileTypes, got %d", len(fileTypes),
+ len(parser.file.FileTypes))
+ }
+
+ // File Checksums
+ codeSha1 := "85ed0817af83a24ad8da68c2b5094de69833983c"
+ sumSha1 := "SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c"
+ codeSha256 := "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
+ sumSha256 := "SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
+ codeMd5 := "624c1abb3664f4b35547e7c73864ad24"
+ sumMd5 := "MD5: 624c1abb3664f4b35547e7c73864ad24"
+ err = parser.parsePairFromFile2_3("FileChecksum", sumSha1)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromFile2_3("FileChecksum", sumSha256)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromFile2_3("FileChecksum", sumMd5)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ for _, checksum := range parser.file.Checksums {
+ switch checksum.Algorithm {
+ case common.SHA1:
+ if checksum.Value != codeSha1 {
+ t.Errorf("expected %s for FileChecksumSHA1, got %s", codeSha1, checksum.Value)
+ }
+ case common.SHA256:
+ if checksum.Value != codeSha256 {
+ t.Errorf("expected %s for FileChecksumSHA1, got %s", codeSha256, checksum.Value)
+ }
+ case common.MD5:
+ if checksum.Value != codeMd5 {
+ t.Errorf("expected %s for FileChecksumSHA1, got %s", codeMd5, checksum.Value)
+ }
+ }
+ }
+ // Concluded License
+ err = parser.parsePairFromFile2_3("LicenseConcluded", "Apache-2.0 OR GPL-2.0-or-later")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.LicenseConcluded != "Apache-2.0 OR GPL-2.0-or-later" {
+ t.Errorf("got %v for LicenseConcluded", parser.file.LicenseConcluded)
+ }
+
+ // License Information in File
+ lics := []string{
+ "Apache-2.0",
+ "GPL-2.0-or-later",
+ "CC0-1.0",
+ }
+ for _, lic := range lics {
+ err = parser.parsePairFromFile2_3("LicenseInfoInFile", lic)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, licWant := range lics {
+ flagFound := false
+ for _, licCheck := range parser.file.LicenseInfoInFiles {
+ if licWant == licCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in LicenseInfoInFiles", licWant)
+ }
+ }
+ if len(lics) != len(parser.file.LicenseInfoInFiles) {
+ t.Errorf("expected %d licenses in LicenseInfoInFiles, got %d", len(lics),
+ len(parser.file.LicenseInfoInFiles))
+ }
+
+ // Comments on License
+ err = parser.parsePairFromFile2_3("LicenseComments", "this is a comment")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.LicenseComments != "this is a comment" {
+ t.Errorf("got %v for LicenseComments", parser.file.LicenseComments)
+ }
+
+ // Copyright Text
+ err = parser.parsePairFromFile2_3("FileCopyrightText", "copyright (c) me")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.FileCopyrightText != "copyright (c) me" {
+ t.Errorf("got %v for FileCopyrightText", parser.file.FileCopyrightText)
+ }
+
+ // Artifact of Projects: Name, HomePage and URI
+ // Artifact set 1
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectName", "project1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectHomePage", "http://example.com/1/")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectURI", "http://example.com/1/uri.whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // Artifact set 2 -- just name
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectName", "project2")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // Artifact set 3 -- just name and home page
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectName", "project3")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectHomePage", "http://example.com/3/")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // Artifact set 4 -- just name and URI
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectName", "project4")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectURI", "http://example.com/4/uri.whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+
+ if len(parser.file.ArtifactOfProjects) != 4 {
+ t.Fatalf("expected len %d, got %d", 4, len(parser.file.ArtifactOfProjects))
+ }
+
+ aop := parser.file.ArtifactOfProjects[0]
+ if aop.Name != "project1" {
+ t.Errorf("expected %v, got %v", "project1", aop.Name)
+ }
+ if aop.HomePage != "http://example.com/1/" {
+ t.Errorf("expected %v, got %v", "http://example.com/1/", aop.HomePage)
+ }
+ if aop.URI != "http://example.com/1/uri.whatever" {
+ t.Errorf("expected %v, got %v", "http://example.com/1/uri.whatever", aop.URI)
+ }
+
+ aop = parser.file.ArtifactOfProjects[1]
+ if aop.Name != "project2" {
+ t.Errorf("expected %v, got %v", "project2", aop.Name)
+ }
+ if aop.HomePage != "" {
+ t.Errorf("expected %v, got %v", "", aop.HomePage)
+ }
+ if aop.URI != "" {
+ t.Errorf("expected %v, got %v", "", aop.URI)
+ }
+
+ aop = parser.file.ArtifactOfProjects[2]
+ if aop.Name != "project3" {
+ t.Errorf("expected %v, got %v", "project3", aop.Name)
+ }
+ if aop.HomePage != "http://example.com/3/" {
+ t.Errorf("expected %v, got %v", "http://example.com/3/", aop.HomePage)
+ }
+ if aop.URI != "" {
+ t.Errorf("expected %v, got %v", "", aop.URI)
+ }
+
+ aop = parser.file.ArtifactOfProjects[3]
+ if aop.Name != "project4" {
+ t.Errorf("expected %v, got %v", "project4", aop.Name)
+ }
+ if aop.HomePage != "" {
+ t.Errorf("expected %v, got %v", "", aop.HomePage)
+ }
+ if aop.URI != "http://example.com/4/uri.whatever" {
+ t.Errorf("expected %v, got %v", "http://example.com/4/uri.whatever", aop.URI)
+ }
+
+ // File Comment
+ err = parser.parsePairFromFile2_3("FileComment", "this is a comment")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.FileComment != "this is a comment" {
+ t.Errorf("got %v for FileComment", parser.file.FileComment)
+ }
+
+ // File Notice
+ err = parser.parsePairFromFile2_3("FileNotice", "this is a Notice")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.file.FileNotice != "this is a Notice" {
+ t.Errorf("got %v for FileNotice", parser.file.FileNotice)
+ }
+
+ // File Contributor
+ contribs := []string{
+ "John Doe jdoe@example.com",
+ "EvilCorp",
+ }
+ for _, contrib := range contribs {
+ err = parser.parsePairFromFile2_3("FileContributor", contrib)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, contribWant := range contribs {
+ flagFound := false
+ for _, contribCheck := range parser.file.FileContributors {
+ if contribWant == contribCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in FileContributors", contribWant)
+ }
+ }
+ if len(contribs) != len(parser.file.FileContributors) {
+ t.Errorf("expected %d contribenses in FileContributors, got %d", len(contribs),
+ len(parser.file.FileContributors))
+ }
+
+ // File Dependencies
+ deps := []string{
+ "f-1.txt",
+ "g.txt",
+ }
+ for _, dep := range deps {
+ err = parser.parsePairFromFile2_3("FileDependency", dep)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, depWant := range deps {
+ flagFound := false
+ for _, depCheck := range parser.file.FileDependencies {
+ if depWant == depCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in FileDependency", depWant)
+ }
+ }
+ if len(deps) != len(parser.file.FileDependencies) {
+ t.Errorf("expected %d depenses in FileDependency, got %d", len(deps),
+ len(parser.file.FileDependencies))
+ }
+
+ // File Attribution Texts
+ attrs := []string{
+ "Include this notice in all advertising materials",
+ "This is a \nmulti-line string",
+ }
+ for _, attr := range attrs {
+ err = parser.parsePairFromFile2_3("FileAttributionText", attr)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, attrWant := range attrs {
+ flagFound := false
+ for _, attrCheck := range parser.file.FileAttributionTexts {
+ if attrWant == attrCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in FileAttributionText", attrWant)
+ }
+ }
+ if len(attrs) != len(parser.file.FileAttributionTexts) {
+ t.Errorf("expected %d attribution texts in FileAttributionTexts, got %d", len(attrs),
+ len(parser.file.FileAttributionTexts))
+ }
+
+}
+
+func TestParser2_3FileCreatesRelationshipInDocument(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-whatever")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.rln == nil {
+ t.Fatalf("parser didn't create and point to Relationship struct")
+ }
+ if parser.rln != parser.doc.Relationships[0] {
+ t.Errorf("pointer to new Relationship doesn't match idx 0 for doc.Relationships[]")
+ }
+}
+
+func TestParser2_3FileCreatesAnnotationInDocument(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.ann == nil {
+ t.Fatalf("parser didn't create and point to Annotation struct")
+ }
+ if parser.ann != parser.doc.Annotations[0] {
+ t.Errorf("pointer to new Annotation doesn't match idx 0 for doc.Annotations[]")
+ }
+}
+
+func TestParser2_3FileUnknownTagFails(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePairFromFile2_3("blah", "something")
+ if err == nil {
+ t.Errorf("expected error from parsing unknown tag")
+ }
+}
+
+func TestFileAOPPointerChangesAfterTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePairFromFile2_3("ArtifactOfProjectName", "project1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.fileAOP == nil {
+ t.Errorf("expected non-nil AOP pointer, got nil")
+ }
+ curPtr := parser.fileAOP
+
+ // now, a home page; pointer should stay
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectHomePage", "http://example.com/1/")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.fileAOP != curPtr {
+ t.Errorf("expected no change in AOP pointer, was %v, got %v", curPtr, parser.fileAOP)
+ }
+
+ // a URI; pointer should stay
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectURI", "http://example.com/1/uri.whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.fileAOP != curPtr {
+ t.Errorf("expected no change in AOP pointer, was %v, got %v", curPtr, parser.fileAOP)
+ }
+
+ // now, another artifact name; pointer should change but be non-nil
+ // now, a home page; pointer should stay
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectName", "project2")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.fileAOP == curPtr {
+ t.Errorf("expected change in AOP pointer, got no change")
+ }
+
+ // finally, an unrelated tag; pointer should go away
+ err = parser.parsePairFromFile2_3("FileComment", "whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.fileAOP != nil {
+ t.Errorf("expected nil AOP pointer, got %v", parser.fileAOP)
+ }
+}
+
+func TestParser2_3FailsIfInvalidSPDXIDInFileSection(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // start with File Name
+ err := parser.parsePairFromFile2_3("FileName", "f1.txt")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid SPDX Identifier
+ err = parser.parsePairFromFile2_3("SPDXID", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfInvalidChecksumFormatInFileSection(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // start with File Name
+ err := parser.parsePairFromFile2_3("FileName", "f1.txt")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid format for checksum line, missing colon
+ err = parser.parsePairFromFile2_3("FileChecksum", "SHA1 85ed0817af83a24ad8da68c2b5094de69833983c")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_1FailsIfUnknownChecksumTypeInFileSection(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // start with File Name
+ err := parser.parsePairFromFile2_3("FileName", "f1.txt")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // unknown checksum type
+ err = parser.parsePairFromFile2_3("FileChecksum", "Special: 85ed0817af83a24ad8da68c2b5094de69833983c")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfArtifactHomePageBeforeArtifactName(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // start with File Name
+ err := parser.parsePairFromFile2_3("FileName", "f1.txt")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // artifact home page appears before artifact name
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectHomePage", "https://example.com")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfArtifactURIBeforeArtifactName(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // start with File Name
+ err := parser.parsePairFromFile2_3("FileName", "f1.txt")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // artifact home page appears before artifact name
+ err = parser.parsePairFromFile2_3("ArtifactOfProjectURI", "https://example.com")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FilesWithoutSpdxIdThrowError(t *testing.T) {
+ // case 1: The previous file (packaged or unpackaged) does not contain spdx ID
+ parser1 := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ file: &v2_3.File{FileName: "FileName"},
+ }
+
+ err := parser1.parsePair2_3("FileName", "f2")
+ if err == nil {
+ t.Errorf("file without SPDX Identifier getting accepted")
+ }
+
+ // case 2: Invalid file with snippet
+ // Last unpackaged file before the snippet start
+ fileName := "f2.txt"
+ sid1 := common.ElementID("s1")
+ parser2 := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ file: &v2_3.File{FileName: fileName},
+ }
+ err = parser2.parsePair2_3("SnippetSPDXID", string(sid1))
+ if err == nil {
+ t.Errorf("file without SPDX Identifier getting accepted")
+ }
+
+ // case 3: Invalid File without snippets
+ // Last unpackaged file before the package starts
+ // Last file of a package and New package starts
+ parser3 := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ fileName = "f3.txt"
+ err = parser3.parsePair2_3("FileName", fileName)
+ if err != nil {
+ t.Errorf("%s", err)
+ }
+ err = parser3.parsePair2_3("PackageName", "p2")
+ if err == nil {
+ t.Errorf("file without SPDX Identifier getting accepted")
+ }
+}
diff --git a/tvloader/parser2v3/parse_other_license.go b/tvloader/parser2v3/parse_other_license.go
new file mode 100644
index 0000000..d2f41ea
--- /dev/null
+++ b/tvloader/parser2v3/parse_other_license.go
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+func (parser *tvParser2_3) parsePairFromOtherLicense2_3(tag string, value string) error {
+ switch tag {
+ // tag for creating new other license section
+ case "LicenseID":
+ parser.otherLic = &v2_3.OtherLicense{}
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.otherLic.LicenseIdentifier = value
+ case "ExtractedText":
+ parser.otherLic.ExtractedText = value
+ case "LicenseName":
+ parser.otherLic.LicenseName = value
+ case "LicenseCrossReference":
+ parser.otherLic.LicenseCrossReferences = append(parser.otherLic.LicenseCrossReferences, value)
+ case "LicenseComment":
+ parser.otherLic.LicenseComment = value
+ // for relationship tags, pass along but don't change state
+ case "Relationship":
+ parser.rln = &v2_3.Relationship{}
+ parser.doc.Relationships = append(parser.doc.Relationships, parser.rln)
+ return parser.parsePairForRelationship2_3(tag, value)
+ case "RelationshipComment":
+ return parser.parsePairForRelationship2_3(tag, value)
+ // for annotation tags, pass along but don't change state
+ case "Annotator":
+ parser.ann = &v2_3.Annotation{}
+ parser.doc.Annotations = append(parser.doc.Annotations, parser.ann)
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationDate":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationType":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "SPDXREF":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationComment":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ // tag for going on to review section (DEPRECATED)
+ case "Reviewer":
+ parser.st = psReview2_3
+ return parser.parsePairFromReview2_3(tag, value)
+ default:
+ return fmt.Errorf("received unknown tag %v in OtherLicense section", tag)
+ }
+
+ return nil
+}
diff --git a/tvloader/parser2v3/parse_other_license_test.go b/tvloader/parser2v3/parse_other_license_test.go
new file mode 100644
index 0000000..2939e67
--- /dev/null
+++ b/tvloader/parser2v3/parse_other_license_test.go
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Parser other license section state change tests =====
+func TestParser2_3OLStartsNewOtherLicenseAfterParsingLicenseIDTag(t *testing.T) {
+ // create the first other license
+ olid1 := "LicenseRef-Lic11"
+ olname1 := "License 11"
+
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: olid1,
+ LicenseName: olname1,
+ },
+ }
+ olic1 := parser.otherLic
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ // the Document's OtherLicenses should have this one only
+ if parser.doc.OtherLicenses[0] != olic1 {
+ t.Errorf("Expected other license %v in OtherLicenses[0], got %v", olic1, parser.doc.OtherLicenses[0])
+ }
+ if parser.doc.OtherLicenses[0].LicenseName != olname1 {
+ t.Errorf("expected other license name %s in OtherLicenses[0], got %s", olname1, parser.doc.OtherLicenses[0].LicenseName)
+ }
+
+ // now add a new other license
+ olid2 := "LicenseRef-22"
+ olname2 := "License 22"
+ err := parser.parsePair2_3("LicenseID", olid2)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+ // and an other license should be created
+ if parser.otherLic == nil {
+ t.Fatalf("parser didn't create new other license")
+ }
+ // also parse the new license's name
+ err = parser.parsePair2_3("LicenseName", olname2)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should still be correct
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+ // and the other license name should be as expected
+ if parser.otherLic.LicenseName != olname2 {
+ t.Errorf("expected other license name %s, got %s", olname2, parser.otherLic.LicenseName)
+ }
+ // and the Document's Other Licenses should be of size 2 and have these two
+ if len(parser.doc.OtherLicenses) != 2 {
+ t.Fatalf("Expected OtherLicenses to have len 2, got %d", len(parser.doc.OtherLicenses))
+ }
+ if parser.doc.OtherLicenses[0] != olic1 {
+ t.Errorf("Expected other license %v in OtherLicenses[0], got %v", olic1, parser.doc.OtherLicenses[0])
+ }
+ if parser.doc.OtherLicenses[0].LicenseIdentifier != olid1 {
+ t.Errorf("expected other license ID %s in OtherLicenses[0], got %s", olid1, parser.doc.OtherLicenses[0].LicenseIdentifier)
+ }
+ if parser.doc.OtherLicenses[0].LicenseName != olname1 {
+ t.Errorf("expected other license name %s in OtherLicenses[0], got %s", olname1, parser.doc.OtherLicenses[0].LicenseName)
+ }
+ if parser.doc.OtherLicenses[1] != parser.otherLic {
+ t.Errorf("Expected other license %v in OtherLicenses[1], got %v", parser.otherLic, parser.doc.OtherLicenses[1])
+ }
+ if parser.doc.OtherLicenses[1].LicenseIdentifier != olid2 {
+ t.Errorf("expected other license ID %s in OtherLicenses[1], got %s", olid2, parser.doc.OtherLicenses[1].LicenseIdentifier)
+ }
+ if parser.doc.OtherLicenses[1].LicenseName != olname2 {
+ t.Errorf("expected other license name %s in OtherLicenses[1], got %s", olname2, parser.doc.OtherLicenses[1].LicenseName)
+ }
+}
+
+func TestParser2_3OLMovesToReviewAfterParsingReviewerTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ err := parser.parsePair2_3("Reviewer", "Person: John Doe")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+}
+
+func TestParser2_3OtherLicenseStaysAfterParsingRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-whatever",
+ LicenseName: "the whatever license",
+ },
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-else")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should remain unchanged
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+ // and the relationship should be in the Document's Relationships
+ if len(parser.doc.Relationships) != 1 {
+ t.Fatalf("expected doc.Relationships to have len 1, got %d", len(parser.doc.Relationships))
+ }
+ deID := parser.doc.Relationships[0].RefA
+ if deID.DocumentRefID != "" || deID.ElementRefID != "blah" {
+ t.Errorf("expected RefA to be %s, got %s", "blah", parser.doc.Relationships[0].RefA)
+ }
+
+ err = parser.parsePair2_3("RelationshipComment", "blah")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should still remain unchanged
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+}
+
+func TestParser2_3OtherLicenseStaysAfterParsingAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-whatever",
+ LicenseName: "the whatever license",
+ },
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psOtherLicense2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationDate", "2018-09-15T00:36:00Z")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psOtherLicense2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psOtherLicense2_3)
+ }
+
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psOtherLicense2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationComment", "i guess i had something to say about this particular file")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psOtherLicense2_3)
+ }
+
+ // and the annotation should be in the Document's Annotations
+ if len(parser.doc.Annotations) != 1 {
+ t.Fatalf("expected doc.Annotations to have len 1, got %d", len(parser.doc.Annotations))
+ }
+ if parser.doc.Annotations[0].Annotator.Annotator != "John Doe ()" {
+ t.Errorf("expected Annotator to be %s, got %s", "John Doe ()", parser.doc.Annotations[0].Annotator)
+ }
+}
+
+func TestParser2_3OLFailsAfterParsingOtherSectionTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ // can't go back to old sections
+ err := parser.parsePair2_3("SPDXVersion", "SPDX-2.3")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+ err = parser.parsePair2_3("PackageName", "whatever")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+ err = parser.parsePair2_3("FileName", "whatever")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+}
+
+// ===== Other License data section tests =====
+func TestParser2_3CanParseOtherLicenseTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ // License Identifier
+ err := parser.parsePairFromOtherLicense2_3("LicenseID", "LicenseRef-Lic11")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.otherLic.LicenseIdentifier != "LicenseRef-Lic11" {
+ t.Errorf("got %v for LicenseID", parser.otherLic.LicenseIdentifier)
+ }
+
+ // Extracted Text
+ err = parser.parsePairFromOtherLicense2_3("ExtractedText", "You are permitted to do anything with the software, hooray!")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.otherLic.ExtractedText != "You are permitted to do anything with the software, hooray!" {
+ t.Errorf("got %v for ExtractedText", parser.otherLic.ExtractedText)
+ }
+
+ // License Name
+ err = parser.parsePairFromOtherLicense2_3("LicenseName", "License 11")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.otherLic.LicenseName != "License 11" {
+ t.Errorf("got %v for LicenseName", parser.otherLic.LicenseName)
+ }
+
+ // License Cross Reference
+ crossRefs := []string{
+ "https://example.com/1",
+ "https://example.com/2",
+ "https://example.com/3",
+ }
+ for _, cr := range crossRefs {
+ err = parser.parsePairFromOtherLicense2_3("LicenseCrossReference", cr)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, refWant := range crossRefs {
+ flagFound := false
+ for _, refCheck := range parser.otherLic.LicenseCrossReferences {
+ if refWant == refCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in LicenseCrossReferences", refWant)
+ }
+ }
+ if len(crossRefs) != len(parser.otherLic.LicenseCrossReferences) {
+ t.Errorf("expected %d types in LicenseCrossReferences, got %d", len(crossRefs),
+ len(parser.otherLic.LicenseCrossReferences))
+ }
+
+ // License Comment
+ err = parser.parsePairFromOtherLicense2_3("LicenseComment", "this is a comment")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.otherLic.LicenseComment != "this is a comment" {
+ t.Errorf("got %v for LicenseComment", parser.otherLic.LicenseComment)
+ }
+}
+
+func TestParser2_3OLUnknownTagFails(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psOtherLicense2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+
+ err := parser.parsePairFromOtherLicense2_3("blah", "something")
+ if err == nil {
+ t.Errorf("expected error from parsing unknown tag")
+ }
+}
diff --git a/tvloader/parser2v3/parse_package.go b/tvloader/parser2v3/parse_package.go
new file mode 100644
index 0000000..d7c87e1
--- /dev/null
+++ b/tvloader/parser2v3/parse_package.go
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+func (parser *tvParser2_3) parsePairFromPackage2_3(tag string, value string) error {
+ // expire pkgExtRef for anything other than a comment
+ // (we'll actually handle the comment further below)
+ if tag != "ExternalRefComment" {
+ parser.pkgExtRef = nil
+ }
+
+ switch tag {
+ case "PackageName":
+ // if package already has a name, create and go on to a new package
+ if parser.pkg == nil || parser.pkg.PackageName != "" {
+ // check if the previous package contained an spdx Id or not
+ if parser.pkg != nil && parser.pkg.PackageSPDXIdentifier == nullSpdxElementId2_3 {
+ return fmt.Errorf("package with PackageName %s does not have SPDX identifier", parser.pkg.PackageName)
+ }
+ parser.pkg = &v2_3.Package{
+ FilesAnalyzed: true,
+ IsFilesAnalyzedTagPresent: false,
+ }
+ }
+ parser.pkg.PackageName = value
+ // tag for going on to file section
+ case "FileName":
+ parser.st = psFile2_3
+ return parser.parsePairFromFile2_3(tag, value)
+ // tag for going on to other license section
+ case "LicenseID":
+ parser.st = psOtherLicense2_3
+ return parser.parsePairFromOtherLicense2_3(tag, value)
+ case "SPDXID":
+ eID, err := extractElementID(value)
+ if err != nil {
+ return err
+ }
+ parser.pkg.PackageSPDXIdentifier = eID
+ if parser.doc.Packages == nil {
+ parser.doc.Packages = []*v2_3.Package{}
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ case "PackageVersion":
+ parser.pkg.PackageVersion = value
+ case "PackageFileName":
+ parser.pkg.PackageFileName = value
+ case "PackageSupplier":
+ supplier := &common.Supplier{Supplier: value}
+ if value == "NOASSERTION" {
+ parser.pkg.PackageSupplier = supplier
+ break
+ }
+
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ switch subkey {
+ case "Person", "Organization":
+ supplier.Supplier = subvalue
+ supplier.SupplierType = subkey
+ default:
+ return fmt.Errorf("unrecognized PackageSupplier type %v", subkey)
+ }
+ parser.pkg.PackageSupplier = supplier
+ case "PackageOriginator":
+ originator := &common.Originator{Originator: value}
+ if value == "NOASSERTION" {
+ parser.pkg.PackageOriginator = originator
+ break
+ }
+
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ switch subkey {
+ case "Person", "Organization":
+ originator.Originator = subvalue
+ originator.OriginatorType = subkey
+ default:
+ return fmt.Errorf("unrecognized PackageOriginator type %v", subkey)
+ }
+ parser.pkg.PackageOriginator = originator
+ case "PackageDownloadLocation":
+ parser.pkg.PackageDownloadLocation = value
+ case "FilesAnalyzed":
+ parser.pkg.IsFilesAnalyzedTagPresent = true
+ if value == "false" {
+ parser.pkg.FilesAnalyzed = false
+ } else if value == "true" {
+ parser.pkg.FilesAnalyzed = true
+ }
+ case "PackageVerificationCode":
+ parser.pkg.PackageVerificationCode = extractCodeAndExcludes(value)
+ case "PackageChecksum":
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ if parser.pkg.PackageChecksums == nil {
+ parser.pkg.PackageChecksums = []common.Checksum{}
+ }
+ switch common.ChecksumAlgorithm(subkey) {
+ case common.SHA1, common.SHA256, common.MD5:
+ algorithm := common.ChecksumAlgorithm(subkey)
+ parser.pkg.PackageChecksums = append(parser.pkg.PackageChecksums, common.Checksum{Algorithm: algorithm, Value: subvalue})
+ default:
+ return fmt.Errorf("got unknown checksum type %s", subkey)
+ }
+ case "PackageHomePage":
+ parser.pkg.PackageHomePage = value
+ case "PackageSourceInfo":
+ parser.pkg.PackageSourceInfo = value
+ case "PackageLicenseConcluded":
+ parser.pkg.PackageLicenseConcluded = value
+ case "PackageLicenseInfoFromFiles":
+ parser.pkg.PackageLicenseInfoFromFiles = append(parser.pkg.PackageLicenseInfoFromFiles, value)
+ case "PackageLicenseDeclared":
+ parser.pkg.PackageLicenseDeclared = value
+ case "PackageLicenseComments":
+ parser.pkg.PackageLicenseComments = value
+ case "PackageCopyrightText":
+ parser.pkg.PackageCopyrightText = value
+ case "PackageSummary":
+ parser.pkg.PackageSummary = value
+ case "PackageDescription":
+ parser.pkg.PackageDescription = value
+ case "PackageComment":
+ parser.pkg.PackageComment = value
+ case "PrimaryPackagePurpose":
+ parser.pkg.PrimaryPackagePurpose = value
+ case "ReleaseDate":
+ parser.pkg.ReleaseDate = value
+ case "BuiltDate":
+ parser.pkg.BuiltDate = value
+ case "ValidUntilDate":
+ parser.pkg.ValidUntilDate = value
+ case "PackageAttributionText":
+ parser.pkg.PackageAttributionTexts = append(parser.pkg.PackageAttributionTexts, value)
+ case "ExternalRef":
+ parser.pkgExtRef = &v2_3.PackageExternalReference{}
+ parser.pkg.PackageExternalReferences = append(parser.pkg.PackageExternalReferences, parser.pkgExtRef)
+ category, refType, locator, err := extractPackageExternalReference(value)
+ if err != nil {
+ return err
+ }
+ parser.pkgExtRef.Category = category
+ parser.pkgExtRef.RefType = refType
+ parser.pkgExtRef.Locator = locator
+ case "ExternalRefComment":
+ if parser.pkgExtRef == nil {
+ return fmt.Errorf("no current ExternalRef found")
+ }
+ parser.pkgExtRef.ExternalRefComment = value
+ // now, expire pkgExtRef anyway because it can have at most one comment
+ parser.pkgExtRef = nil
+ // for relationship tags, pass along but don't change state
+ case "Relationship":
+ parser.rln = &v2_3.Relationship{}
+ parser.doc.Relationships = append(parser.doc.Relationships, parser.rln)
+ return parser.parsePairForRelationship2_3(tag, value)
+ case "RelationshipComment":
+ return parser.parsePairForRelationship2_3(tag, value)
+ // for annotation tags, pass along but don't change state
+ case "Annotator":
+ parser.ann = &v2_3.Annotation{}
+ parser.doc.Annotations = append(parser.doc.Annotations, parser.ann)
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationDate":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationType":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "SPDXREF":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationComment":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ // tag for going on to review section (DEPRECATED)
+ case "Reviewer":
+ parser.st = psReview2_3
+ return parser.parsePairFromReview2_3(tag, value)
+ default:
+ return fmt.Errorf("received unknown tag %v in Package section", tag)
+ }
+
+ return nil
+}
+
+// ===== Helper functions =====
+
+func extractCodeAndExcludes(value string) *common.PackageVerificationCode {
+ // FIXME this should probably be done using regular expressions instead
+ // split by paren + word "excludes:"
+ sp := strings.SplitN(value, "(excludes:", 2)
+ if len(sp) < 2 {
+ // not found; return the whole string as just the code
+ return &common.PackageVerificationCode{Value: value, ExcludedFiles: []string{}}
+ }
+
+ // if we're here, code is in first part and excludes filename is in
+ // second part, with trailing paren
+ code := strings.TrimSpace(sp[0])
+ parsedSp := strings.SplitN(sp[1], ")", 2)
+ fileName := strings.TrimSpace(parsedSp[0])
+ return &common.PackageVerificationCode{Value: code, ExcludedFiles: []string{fileName}}
+}
+
+func extractPackageExternalReference(value string) (string, string, string, error) {
+ sp := strings.Split(value, " ")
+ // remove any that are just whitespace
+ keepSp := []string{}
+ for _, s := range sp {
+ ss := strings.TrimSpace(s)
+ if ss != "" {
+ keepSp = append(keepSp, ss)
+ }
+ }
+ // now, should have 3 items and should be able to map them
+ if len(keepSp) != 3 {
+ return "", "", "", fmt.Errorf("expected 3 elements, got %d", len(keepSp))
+ }
+ return keepSp[0], keepSp[1], keepSp[2], nil
+}
diff --git a/tvloader/parser2v3/parse_package_test.go b/tvloader/parser2v3/parse_package_test.go
new file mode 100644
index 0000000..714e61f
--- /dev/null
+++ b/tvloader/parser2v3/parse_package_test.go
@@ -0,0 +1,1130 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Parser package section state change tests =====
+func TestParser2_3PackageStartsNewPackageAfterParsingPackageNameTag(t *testing.T) {
+ // create the first package
+ pkgOldName := "p1"
+
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: pkgOldName, PackageSPDXIdentifier: "p1"},
+ }
+ pkgOld := parser.pkg
+ parser.doc.Packages = append(parser.doc.Packages, pkgOld)
+ // the Document's Packages should have this one only
+ if parser.doc.Packages[0] != pkgOld {
+ t.Errorf("expected package %v, got %v", pkgOld, parser.doc.Packages[0])
+ }
+ if len(parser.doc.Packages) != 1 {
+ t.Errorf("expected 1 package, got %d", len(parser.doc.Packages))
+ }
+
+ // now add a new package
+ pkgName := "p2"
+ err := parser.parsePair2_3("PackageName", pkgName)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psPackage2_3 {
+ t.Errorf("expected state to be %v, got %v", psPackage2_3, parser.st)
+ }
+ // and a package should be created
+ if parser.pkg == nil {
+ t.Fatalf("parser didn't create new package")
+ }
+ // and it should not be pkgOld
+ if parser.pkg == pkgOld {
+ t.Errorf("expected new package, got pkgOld")
+ }
+ // and the package name should be as expected
+ if parser.pkg.PackageName != pkgName {
+ t.Errorf("expected package name %s, got %s", pkgName, parser.pkg.PackageName)
+ }
+ // and the package should default to true for FilesAnalyzed
+ if parser.pkg.FilesAnalyzed != true {
+ t.Errorf("expected FilesAnalyzed to default to true, got false")
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != false {
+ t.Errorf("expected IsFilesAnalyzedTagPresent to default to false, got true")
+ }
+ // and the Document's Packages should still be of size 1 and have pkgOld only
+ if parser.doc.Packages[0] != pkgOld {
+ t.Errorf("Expected package %v, got %v", pkgOld, parser.doc.Packages[0])
+ }
+ if len(parser.doc.Packages) != 1 {
+ t.Errorf("expected 1 package, got %d", len(parser.doc.Packages))
+ }
+}
+
+func TestParser2_3PackageStartsNewPackageAfterParsingPackageNameTagWhileInUnpackaged(t *testing.T) {
+ // pkg is nil, so that Files appearing before the first PackageName tag
+ // are added to Files instead of Packages
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psFile2_3,
+ pkg: nil,
+ }
+ // the Document's Packages should be empty
+ if len(parser.doc.Packages) != 0 {
+ t.Errorf("Expected zero packages, got %d", len(parser.doc.Packages))
+ }
+
+ // now add a new package
+ pkgName := "p2"
+ err := parser.parsePair2_3("PackageName", pkgName)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psPackage2_3 {
+ t.Errorf("expected state to be %v, got %v", psPackage2_3, parser.st)
+ }
+ // and a package should be created
+ if parser.pkg == nil {
+ t.Fatalf("parser didn't create new package")
+ }
+ // and the package name should be as expected
+ if parser.pkg.PackageName != pkgName {
+ t.Errorf("expected package name %s, got %s", pkgName, parser.pkg.PackageName)
+ }
+ // and the package should default to true for FilesAnalyzed
+ if parser.pkg.FilesAnalyzed != true {
+ t.Errorf("expected FilesAnalyzed to default to true, got false")
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != false {
+ t.Errorf("expected IsFilesAnalyzedTagPresent to default to false, got true")
+ }
+ // and the Document's Packages should be of size 0, because the prior was
+ // unpackaged files and this one won't be added until an SPDXID is seen
+ if len(parser.doc.Packages) != 0 {
+ t.Errorf("Expected %v packages in doc, got %v", 0, len(parser.doc.Packages))
+ }
+}
+
+func TestParser2_3PackageMovesToFileAfterParsingFileNameTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ pkgCurrent := parser.pkg
+
+ err := parser.parsePair2_3("FileName", "testFile")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psFile2_3 {
+ t.Errorf("expected state to be %v, got %v", psFile2_3, parser.st)
+ }
+ // and current package should remain what it was
+ if parser.pkg != pkgCurrent {
+ t.Fatalf("expected package to remain %v, got %v", pkgCurrent, parser.pkg)
+ }
+}
+
+func TestParser2_3PackageMovesToOtherLicenseAfterParsingLicenseIDTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePair2_3("LicenseID", "LicenseRef-TestLic")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+}
+
+func TestParser2_3PackageMovesToReviewAfterParsingReviewerTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePair2_3("Reviewer", "Person: John Doe")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+}
+
+func TestParser2_3PackageStaysAfterParsingRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-else")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should remain unchanged
+ if parser.st != psPackage2_3 {
+ t.Errorf("expected state to be %v, got %v", psPackage2_3, parser.st)
+ }
+
+ err = parser.parsePair2_3("RelationshipComment", "blah")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should still remain unchanged
+ if parser.st != psPackage2_3 {
+ t.Errorf("expected state to be %v, got %v", psPackage2_3, parser.st)
+ }
+}
+
+func TestParser2_3PackageStaysAfterParsingAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psPackage2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psPackage2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationDate", "2018-09-15T00:36:00Z")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psPackage2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psPackage2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psPackage2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psPackage2_3)
+ }
+
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psPackage2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psPackage2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationComment", "i guess i had something to say about this package")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psPackage2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psPackage2_3)
+ }
+}
+
+// ===== Package data section tests =====
+func TestParser2_3CanParsePackageTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // should not yet be in Packages map, b/c no SPDX identifier
+ if len(parser.doc.Packages) != 0 {
+ t.Errorf("expected 0 packages, got %d", len(parser.doc.Packages))
+ }
+
+ // Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageName != "p1" {
+ t.Errorf("got %v for PackageName", parser.pkg.PackageName)
+ }
+ // still should not yet be in Packages map, b/c no SPDX identifier
+ if len(parser.doc.Packages) != 0 {
+ t.Errorf("expected 0 packages, got %d", len(parser.doc.Packages))
+ }
+
+ // Package SPDX Identifier
+ err = parser.parsePairFromPackage2_3("SPDXID", "SPDXRef-p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // "SPDXRef-" prefix should be removed from the item
+ if parser.pkg.PackageSPDXIdentifier != "p1" {
+ t.Errorf("got %v for PackageSPDXIdentifier", parser.pkg.PackageSPDXIdentifier)
+ }
+ // and it should now be added to the Packages map
+ if len(parser.doc.Packages) != 1 {
+ t.Errorf("expected 1 package, got %d", len(parser.doc.Packages))
+ }
+ if parser.doc.Packages[0] != parser.pkg {
+ t.Errorf("expected to point to parser.pkg, got %v", parser.doc.Packages[0])
+ }
+
+ // Package Version
+ err = parser.parsePairFromPackage2_3("PackageVersion", "2.1.1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageVersion != "2.1.1" {
+ t.Errorf("got %v for PackageVersion", parser.pkg.PackageVersion)
+ }
+
+ // Package File Name
+ err = parser.parsePairFromPackage2_3("PackageFileName", "p1-2.1.1.tar.gz")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageFileName != "p1-2.1.1.tar.gz" {
+ t.Errorf("got %v for PackageFileName", parser.pkg.PackageFileName)
+ }
+
+ // Package Supplier
+ // SKIP -- separate tests for subvalues below
+
+ // Package Originator
+ // SKIP -- separate tests for subvalues below
+
+ // Package Download Location
+ err = parser.parsePairFromPackage2_3("PackageDownloadLocation", "https://example.com/whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageDownloadLocation != "https://example.com/whatever" {
+ t.Errorf("got %v for PackageDownloadLocation", parser.pkg.PackageDownloadLocation)
+ }
+
+ // Files Analyzed
+ err = parser.parsePairFromPackage2_3("FilesAnalyzed", "false")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.FilesAnalyzed != false {
+ t.Errorf("got %v for FilesAnalyzed", parser.pkg.FilesAnalyzed)
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != true {
+ t.Errorf("got %v for IsFilesAnalyzedTagPresent", parser.pkg.IsFilesAnalyzedTagPresent)
+ }
+
+ // Package Verification Code
+ // SKIP -- separate tests for "excludes", or not, below
+
+ // Package Checksums
+ codeSha1 := "85ed0817af83a24ad8da68c2b5094de69833983c"
+ sumSha1 := "SHA1: 85ed0817af83a24ad8da68c2b5094de69833983c"
+ codeSha256 := "11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
+ sumSha256 := "SHA256: 11b6d3ee554eedf79299905a98f9b9a04e498210b59f15094c916c91d150efcd"
+ codeMd5 := "624c1abb3664f4b35547e7c73864ad24"
+ sumMd5 := "MD5: 624c1abb3664f4b35547e7c73864ad24"
+ err = parser.parsePairFromPackage2_3("PackageChecksum", sumSha1)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromPackage2_3("PackageChecksum", sumSha256)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromPackage2_3("PackageChecksum", sumMd5)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ for _, checksum := range parser.pkg.PackageChecksums {
+ switch checksum.Algorithm {
+ case common.SHA1:
+ if checksum.Value != codeSha1 {
+ t.Errorf("expected %s for FileChecksumSHA1, got %s", codeSha1, checksum.Value)
+ }
+ case common.SHA256:
+ if checksum.Value != codeSha256 {
+ t.Errorf("expected %s for FileChecksumSHA1, got %s", codeSha256, checksum.Value)
+ }
+ case common.MD5:
+ if checksum.Value != codeMd5 {
+ t.Errorf("expected %s for FileChecksumSHA1, got %s", codeMd5, checksum.Value)
+ }
+ }
+ }
+
+ // Package Home Page
+ err = parser.parsePairFromPackage2_3("PackageHomePage", "https://example.com/whatever2")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageHomePage != "https://example.com/whatever2" {
+ t.Errorf("got %v for PackageHomePage", parser.pkg.PackageHomePage)
+ }
+
+ // Package Source Info
+ err = parser.parsePairFromPackage2_3("PackageSourceInfo", "random comment")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageSourceInfo != "random comment" {
+ t.Errorf("got %v for PackageSourceInfo", parser.pkg.PackageSourceInfo)
+ }
+
+ // Package License Concluded
+ err = parser.parsePairFromPackage2_3("PackageLicenseConcluded", "Apache-2.0 OR GPL-2.0-or-later")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageLicenseConcluded != "Apache-2.0 OR GPL-2.0-or-later" {
+ t.Errorf("got %v for PackageLicenseConcluded", parser.pkg.PackageLicenseConcluded)
+ }
+
+ // All Licenses Info From Files
+ lics := []string{
+ "Apache-2.0",
+ "GPL-2.0-or-later",
+ "CC0-1.0",
+ }
+ for _, lic := range lics {
+ err = parser.parsePairFromPackage2_3("PackageLicenseInfoFromFiles", lic)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, licWant := range lics {
+ flagFound := false
+ for _, licCheck := range parser.pkg.PackageLicenseInfoFromFiles {
+ if licWant == licCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in PackageLicenseInfoFromFiles", licWant)
+ }
+ }
+ if len(lics) != len(parser.pkg.PackageLicenseInfoFromFiles) {
+ t.Errorf("expected %d licenses in PackageLicenseInfoFromFiles, got %d", len(lics),
+ len(parser.pkg.PackageLicenseInfoFromFiles))
+ }
+
+ // Package License Declared
+ err = parser.parsePairFromPackage2_3("PackageLicenseDeclared", "Apache-2.0 OR GPL-2.0-or-later")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageLicenseDeclared != "Apache-2.0 OR GPL-2.0-or-later" {
+ t.Errorf("got %v for PackageLicenseDeclared", parser.pkg.PackageLicenseDeclared)
+ }
+
+ // Package License Comments
+ err = parser.parsePairFromPackage2_3("PackageLicenseComments", "this is a license comment")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageLicenseComments != "this is a license comment" {
+ t.Errorf("got %v for PackageLicenseComments", parser.pkg.PackageLicenseComments)
+ }
+
+ // Package Copyright Text
+ err = parser.parsePairFromPackage2_3("PackageCopyrightText", "Copyright (c) me myself and i")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageCopyrightText != "Copyright (c) me myself and i" {
+ t.Errorf("got %v for PackageCopyrightText", parser.pkg.PackageCopyrightText)
+ }
+
+ // Package Summary
+ err = parser.parsePairFromPackage2_3("PackageSummary", "i wrote this package")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageSummary != "i wrote this package" {
+ t.Errorf("got %v for PackageSummary", parser.pkg.PackageSummary)
+ }
+
+ // Package Description
+ err = parser.parsePairFromPackage2_3("PackageDescription", "i wrote this package a lot")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageDescription != "i wrote this package a lot" {
+ t.Errorf("got %v for PackageDescription", parser.pkg.PackageDescription)
+ }
+
+ // Package Comment
+ err = parser.parsePairFromPackage2_3("PackageComment", "i scanned this package")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageComment != "i scanned this package" {
+ t.Errorf("got %v for PackageComment", parser.pkg.PackageComment)
+ }
+
+ // Package Attribution Text
+ attrs := []string{
+ "Include this notice in all advertising materials",
+ "This is a \nmulti-line string",
+ }
+ for _, attr := range attrs {
+ err = parser.parsePairFromPackage2_3("PackageAttributionText", attr)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, attrWant := range attrs {
+ flagFound := false
+ for _, attrCheck := range parser.pkg.PackageAttributionTexts {
+ if attrWant == attrCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in PackageAttributionText", attrWant)
+ }
+ }
+ if len(attrs) != len(parser.pkg.PackageAttributionTexts) {
+ t.Errorf("expected %d attribution texts in PackageAttributionTexts, got %d", len(attrs),
+ len(parser.pkg.PackageAttributionTexts))
+ }
+
+ // Package External References and Comments
+ ref1 := "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+ ref1Category := "SECURITY"
+ ref1Type := "cpe23Type"
+ ref1Locator := "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+ ref1Comment := "this is comment #1"
+ ref2 := "OTHER LocationRef-acmeforge acmecorp/acmenator/4.1.3alpha"
+ ref2Category := "OTHER"
+ ref2Type := "LocationRef-acmeforge"
+ ref2Locator := "acmecorp/acmenator/4.1.3alpha"
+ ref2Comment := "this is comment #2"
+ err = parser.parsePairFromPackage2_3("ExternalRef", ref1)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if len(parser.pkg.PackageExternalReferences) != 1 {
+ t.Errorf("expected 1 external reference, got %d", len(parser.pkg.PackageExternalReferences))
+ }
+ if parser.pkgExtRef == nil {
+ t.Errorf("expected non-nil pkgExtRef, got nil")
+ }
+ if parser.pkg.PackageExternalReferences[0] == nil {
+ t.Errorf("expected non-nil PackageExternalReferences[0], got nil")
+ }
+ if parser.pkgExtRef != parser.pkg.PackageExternalReferences[0] {
+ t.Errorf("expected pkgExtRef to match PackageExternalReferences[0], got no match")
+ }
+ err = parser.parsePairFromPackage2_3("ExternalRefComment", ref1Comment)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ err = parser.parsePairFromPackage2_3("ExternalRef", ref2)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if len(parser.pkg.PackageExternalReferences) != 2 {
+ t.Errorf("expected 2 external references, got %d", len(parser.pkg.PackageExternalReferences))
+ }
+ if parser.pkgExtRef == nil {
+ t.Errorf("expected non-nil pkgExtRef, got nil")
+ }
+ if parser.pkg.PackageExternalReferences[1] == nil {
+ t.Errorf("expected non-nil PackageExternalReferences[1], got nil")
+ }
+ if parser.pkgExtRef != parser.pkg.PackageExternalReferences[1] {
+ t.Errorf("expected pkgExtRef to match PackageExternalReferences[1], got no match")
+ }
+ err = parser.parsePairFromPackage2_3("ExternalRefComment", ref2Comment)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // finally, check these values
+ gotRef1 := parser.pkg.PackageExternalReferences[0]
+ if gotRef1.Category != ref1Category {
+ t.Errorf("expected ref1 category to be %s, got %s", gotRef1.Category, ref1Category)
+ }
+ if gotRef1.RefType != ref1Type {
+ t.Errorf("expected ref1 type to be %s, got %s", gotRef1.RefType, ref1Type)
+ }
+ if gotRef1.Locator != ref1Locator {
+ t.Errorf("expected ref1 locator to be %s, got %s", gotRef1.Locator, ref1Locator)
+ }
+ if gotRef1.ExternalRefComment != ref1Comment {
+ t.Errorf("expected ref1 comment to be %s, got %s", gotRef1.ExternalRefComment, ref1Comment)
+ }
+ gotRef2 := parser.pkg.PackageExternalReferences[1]
+ if gotRef2.Category != ref2Category {
+ t.Errorf("expected ref2 category to be %s, got %s", gotRef2.Category, ref2Category)
+ }
+ if gotRef2.RefType != ref2Type {
+ t.Errorf("expected ref2 type to be %s, got %s", gotRef2.RefType, ref2Type)
+ }
+ if gotRef2.Locator != ref2Locator {
+ t.Errorf("expected ref2 locator to be %s, got %s", gotRef2.Locator, ref2Locator)
+ }
+ if gotRef2.ExternalRefComment != ref2Comment {
+ t.Errorf("expected ref2 comment to be %s, got %s", gotRef2.ExternalRefComment, ref2Comment)
+ }
+
+}
+
+func TestParser2_3CanParsePackageSupplierPersonTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Supplier: Person
+ err := parser.parsePairFromPackage2_3("PackageSupplier", "Person: John Doe")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageSupplier.Supplier != "John Doe" {
+ t.Errorf("got %v for PackageSupplierPerson", parser.pkg.PackageSupplier.Supplier)
+ }
+}
+
+func TestParser2_3CanParsePackageSupplierOrganizationTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Supplier: Organization
+ err := parser.parsePairFromPackage2_3("PackageSupplier", "Organization: John Doe, Inc.")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageSupplier.Supplier != "John Doe, Inc." {
+ t.Errorf("got %v for PackageSupplierOrganization", parser.pkg.PackageSupplier.Supplier)
+ }
+}
+
+func TestParser2_3CanParsePackageSupplierNOASSERTIONTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Supplier: NOASSERTION
+ err := parser.parsePairFromPackage2_3("PackageSupplier", "NOASSERTION")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageSupplier.Supplier != "NOASSERTION" {
+ t.Errorf("got value for Supplier, expected NOASSERTION")
+ }
+}
+
+func TestParser2_3CanParsePackageOriginatorPersonTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Originator: Person
+ err := parser.parsePairFromPackage2_3("PackageOriginator", "Person: John Doe")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageOriginator.Originator != "John Doe" {
+ t.Errorf("got %v for PackageOriginator", parser.pkg.PackageOriginator.Originator)
+ }
+}
+
+func TestParser2_3CanParsePackageOriginatorOrganizationTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Originator: Organization
+ err := parser.parsePairFromPackage2_3("PackageOriginator", "Organization: John Doe, Inc.")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageOriginator.Originator != "John Doe, Inc." {
+ t.Errorf("got %v for PackageOriginator", parser.pkg.PackageOriginator.Originator)
+ }
+}
+
+func TestParser2_3CanParsePackageOriginatorNOASSERTIONTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Originator: NOASSERTION
+ err := parser.parsePairFromPackage2_3("PackageOriginator", "NOASSERTION")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageOriginator.Originator != "NOASSERTION" {
+ t.Errorf("got false for PackageOriginatorNOASSERTION")
+ }
+}
+
+func TestParser2_3CanParsePackageVerificationCodeTagWithExcludes(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Verification Code with excludes parenthetical
+ code := "d6a770ba38583ed4bb4525bd96e50461655d2758"
+ fileName := "./package.spdx"
+ fullCodeValue := "d6a770ba38583ed4bb4525bd96e50461655d2758 (excludes: ./package.spdx)"
+ err := parser.parsePairFromPackage2_3("PackageVerificationCode", fullCodeValue)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageVerificationCode.Value != code {
+ t.Errorf("got %v for PackageVerificationCode", parser.pkg.PackageVerificationCode)
+ }
+ if len(parser.pkg.PackageVerificationCode.ExcludedFiles) != 1 || parser.pkg.PackageVerificationCode.ExcludedFiles[0] != fileName {
+ t.Errorf("got %v for PackageVerificationCodeExcludedFile", parser.pkg.PackageVerificationCode.ExcludedFiles)
+ }
+
+}
+
+func TestParser2_3CanParsePackageVerificationCodeTagWithoutExcludes(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ // Package Verification Code without excludes parenthetical
+ code := "d6a770ba38583ed4bb4525bd96e50461655d2758"
+ err := parser.parsePairFromPackage2_3("PackageVerificationCode", code)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.PackageVerificationCode.Value != code {
+ t.Errorf("got %v for PackageVerificationCode", parser.pkg.PackageVerificationCode)
+ }
+ if len(parser.pkg.PackageVerificationCode.ExcludedFiles) != 0 {
+ t.Errorf("got %v for PackageVerificationCodeExcludedFile", parser.pkg.PackageVerificationCode.ExcludedFiles)
+ }
+
+}
+
+func TestParser2_3PackageExternalRefPointerChangesAfterTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ ref1 := "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+ err := parser.parsePairFromPackage2_3("ExternalRef", ref1)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkgExtRef == nil {
+ t.Errorf("expected non-nil external reference pointer, got nil")
+ }
+
+ // now, a comment; pointer should go away
+ err = parser.parsePairFromPackage2_3("ExternalRefComment", "whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkgExtRef != nil {
+ t.Errorf("expected nil external reference pointer, got non-nil")
+ }
+
+ ref2 := "Other LocationRef-something https://example.com/whatever"
+ err = parser.parsePairFromPackage2_3("ExternalRef", ref2)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkgExtRef == nil {
+ t.Errorf("expected non-nil external reference pointer, got nil")
+ }
+
+ // and some other random tag makes the pointer go away too
+ err = parser.parsePairFromPackage2_3("PackageSummary", "whatever")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkgExtRef != nil {
+ t.Errorf("expected nil external reference pointer, got non-nil")
+ }
+}
+
+func TestParser2_3PackageCreatesRelationshipInDocument(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-whatever")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.rln == nil {
+ t.Fatalf("parser didn't create and point to Relationship struct")
+ }
+ if parser.rln != parser.doc.Relationships[0] {
+ t.Errorf("pointer to new Relationship doesn't match idx 0 for doc.Relationships[]")
+ }
+}
+
+func TestParser2_3PackageCreatesAnnotationInDocument(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.ann == nil {
+ t.Fatalf("parser didn't create and point to Annotation struct")
+ }
+ if parser.ann != parser.doc.Annotations[0] {
+ t.Errorf("pointer to new Annotation doesn't match idx 0 for doc.Annotations[]")
+ }
+}
+
+func TestParser2_3PackageUnknownTagFails(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: "p1", PackageSPDXIdentifier: "p1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+
+ err := parser.parsePairFromPackage2_3("blah", "something")
+ if err == nil {
+ t.Errorf("expected error from parsing unknown tag")
+ }
+}
+
+func TestParser2_3FailsIfInvalidSPDXIDInPackageSection(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid ID format
+ err = parser.parsePairFromPackage2_3("SPDXID", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfInvalidPackageSupplierFormat(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid supplier format
+ err = parser.parsePairFromPackage2_3("PackageSupplier", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfUnknownPackageSupplierType(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid supplier type
+ err = parser.parsePairFromPackage2_3("PackageSupplier", "whoops: John Doe")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfInvalidPackageOriginatorFormat(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid originator format
+ err = parser.parsePairFromPackage2_3("PackageOriginator", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfUnknownPackageOriginatorType(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid originator type
+ err = parser.parsePairFromPackage2_3("PackageOriginator", "whoops: John Doe")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3SetsFilesAnalyzedTagsCorrectly(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // set tag
+ err = parser.parsePairFromPackage2_3("FilesAnalyzed", "true")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.pkg.FilesAnalyzed != true {
+ t.Errorf("expected %v, got %v", true, parser.pkg.FilesAnalyzed)
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != true {
+ t.Errorf("expected %v, got %v", true, parser.pkg.IsFilesAnalyzedTagPresent)
+ }
+}
+
+func TestParser2_3FailsIfInvalidPackageChecksumFormat(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid checksum format
+ err = parser.parsePairFromPackage2_3("PackageChecksum", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfInvalidPackageChecksumType(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid checksum type
+ err = parser.parsePairFromPackage2_3("PackageChecksum", "whoops: blah")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfInvalidExternalRefFormat(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid external ref format
+ err = parser.parsePairFromPackage2_3("ExternalRef", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfExternalRefCommentBeforeExternalRef(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{},
+ }
+
+ // start with Package Name
+ err := parser.parsePairFromPackage2_3("PackageName", "p1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // external ref comment before external ref
+ err = parser.parsePairFromPackage2_3("ExternalRefComment", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+// ===== Helper function tests =====
+
+func TestCanCheckAndExtractExcludesFilenameAndCode(t *testing.T) {
+ code := "d6a770ba38583ed4bb4525bd96e50461655d2758"
+ fileName := "./package.spdx"
+ fullCodeValue := "d6a770ba38583ed4bb4525bd96e50461655d2758 (excludes: ./package.spdx)"
+
+ gotCode := extractCodeAndExcludes(fullCodeValue)
+ if gotCode.Value != code {
+ t.Errorf("got %v for gotCode", gotCode)
+ }
+ if len(gotCode.ExcludedFiles) != 1 || gotCode.ExcludedFiles[0] != fileName {
+ t.Errorf("got %v for gotFileName", gotCode.ExcludedFiles)
+ }
+}
+
+func TestCanExtractPackageExternalReference(t *testing.T) {
+ ref1 := "SECURITY cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+ category := "SECURITY"
+ refType := "cpe23Type"
+ location := "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+
+ gotCategory, gotRefType, gotLocation, err := extractPackageExternalReference(ref1)
+ if err != nil {
+ t.Errorf("got non-nil error: %v", err)
+ }
+ if gotCategory != category {
+ t.Errorf("expected category %s, got %s", category, gotCategory)
+ }
+ if gotRefType != refType {
+ t.Errorf("expected refType %s, got %s", refType, gotRefType)
+ }
+ if gotLocation != location {
+ t.Errorf("expected location %s, got %s", location, gotLocation)
+ }
+}
+
+func TestCanExtractPackageExternalReferenceWithExtraWhitespace(t *testing.T) {
+ ref1 := " SECURITY \t cpe23Type cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:* \t "
+ category := "SECURITY"
+ refType := "cpe23Type"
+ location := "cpe:2.3:a:pivotal_software:spring_framework:4.1.0:*:*:*:*:*:*:*"
+
+ gotCategory, gotRefType, gotLocation, err := extractPackageExternalReference(ref1)
+ if err != nil {
+ t.Errorf("got non-nil error: %v", err)
+ }
+ if gotCategory != category {
+ t.Errorf("expected category %s, got %s", category, gotCategory)
+ }
+ if gotRefType != refType {
+ t.Errorf("expected refType %s, got %s", refType, gotRefType)
+ }
+ if gotLocation != location {
+ t.Errorf("expected location %s, got %s", location, gotLocation)
+ }
+}
+
+func TestFailsPackageExternalRefWithInvalidFormat(t *testing.T) {
+ _, _, _, err := extractPackageExternalReference("whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3PackageWithoutSpdxIdentifierThrowsError(t *testing.T) {
+ // More than one package, the previous package doesn't contain an SPDX ID
+ pkgOldName := "p1"
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psPackage2_3,
+ pkg: &v2_3.Package{PackageName: pkgOldName},
+ }
+ pkgOld := parser.pkg
+ parser.doc.Packages = append(parser.doc.Packages, pkgOld)
+ // the Document's Packages should have this one only
+ if parser.doc.Packages[0] != pkgOld {
+ t.Errorf("expected package %v, got %v", pkgOld, parser.doc.Packages[0])
+ }
+ if len(parser.doc.Packages) != 1 {
+ t.Errorf("expected 1 package, got %d", len(parser.doc.Packages))
+ }
+
+ pkgName := "p2"
+ err := parser.parsePair2_3("PackageName", pkgName)
+ if err == nil {
+ t.Errorf("package without SPDX Identifier getting accepted")
+ }
+}
diff --git a/tvloader/parser2v3/parse_relationship.go b/tvloader/parser2v3/parse_relationship.go
new file mode 100644
index 0000000..8f49417
--- /dev/null
+++ b/tvloader/parser2v3/parse_relationship.go
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+ "strings"
+)
+
+func (parser *tvParser2_3) parsePairForRelationship2_3(tag string, value string) error {
+ if parser.rln == nil {
+ return fmt.Errorf("no relationship struct created in parser rln pointer")
+ }
+
+ if tag == "Relationship" {
+ // parse the value to see if it's a valid relationship format
+ sp := strings.SplitN(value, " ", -1)
+
+ // filter out any purely-whitespace items
+ var rp []string
+ for _, v := range sp {
+ v = strings.TrimSpace(v)
+ if v != "" {
+ rp = append(rp, v)
+ }
+ }
+
+ if len(rp) != 3 {
+ return fmt.Errorf("invalid relationship format for %s", value)
+ }
+
+ aID, err := extractDocElementID(strings.TrimSpace(rp[0]))
+ if err != nil {
+ return err
+ }
+ parser.rln.RefA = aID
+ parser.rln.Relationship = strings.TrimSpace(rp[1])
+ // NONE and NOASSERTION are permitted on right side
+ permittedSpecial := []string{"NONE", "NOASSERTION"}
+ bID, err := extractDocElementSpecial(strings.TrimSpace(rp[2]), permittedSpecial)
+ if err != nil {
+ return err
+ }
+ parser.rln.RefB = bID
+ return nil
+ }
+
+ if tag == "RelationshipComment" {
+ parser.rln.RelationshipComment = value
+ return nil
+ }
+
+ return fmt.Errorf("received unknown tag %v in Relationship section", tag)
+}
diff --git a/tvloader/parser2v3/parse_relationship_test.go b/tvloader/parser2v3/parse_relationship_test.go
new file mode 100644
index 0000000..57714d1
--- /dev/null
+++ b/tvloader/parser2v3/parse_relationship_test.go
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Relationship section tests =====
+func TestParser2_3FailsIfRelationshipNotSet(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePairForRelationship2_3("Relationship", "SPDXRef-A CONTAINS SPDXRef-B")
+ if err == nil {
+ t.Errorf("expected error when calling parsePairFromRelationship2_3 without setting rln pointer")
+ }
+}
+
+func TestParser2_3FailsIfRelationshipCommentWithoutRelationship(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+ err := parser.parsePair2_3("RelationshipComment", "comment whatever")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3 for RelationshipComment without Relationship first")
+ }
+}
+
+func TestParser2_3CanParseRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // Relationship
+ err := parser.parsePair2_3("Relationship", "SPDXRef-something CONTAINS DocumentRef-otherdoc:SPDXRef-something-else")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rln.RefA.DocumentRefID != "" || parser.rln.RefA.ElementRefID != "something" {
+ t.Errorf("got %v for first part of Relationship, expected something", parser.rln.RefA)
+ }
+ if parser.rln.RefB.DocumentRefID != "otherdoc" || parser.rln.RefB.ElementRefID != "something-else" {
+ t.Errorf("got %v for second part of Relationship, expected otherdoc:something-else", parser.rln.RefB)
+ }
+ if parser.rln.Relationship != "CONTAINS" {
+ t.Errorf("got %v for Relationship type, expected CONTAINS", parser.rln.Relationship)
+ }
+
+ // Relationship Comment
+ cmt := "this is a comment"
+ err = parser.parsePair2_3("RelationshipComment", cmt)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rln.RelationshipComment != cmt {
+ t.Errorf("got %v for RelationshipComment, expected %v", parser.rln.RelationshipComment, cmt)
+ }
+}
+
+func TestParser2_3InvalidRelationshipTagsNoValueFail(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // no items
+ parser.rln = nil
+ err := parser.parsePair2_3("Relationship", "")
+ if err == nil {
+ t.Errorf("expected error for empty items in relationship, got nil")
+ }
+}
+
+func TestParser2_3InvalidRelationshipTagsOneValueFail(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // one item
+ parser.rln = nil
+ err := parser.parsePair2_3("Relationship", "DESCRIBES")
+ if err == nil {
+ t.Errorf("expected error for only one item in relationship, got nil")
+ }
+}
+
+func TestParser2_3InvalidRelationshipTagsTwoValuesFail(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // two items
+ parser.rln = nil
+ err := parser.parsePair2_3("Relationship", "SPDXRef-DOCUMENT DESCRIBES")
+ if err == nil {
+ t.Errorf("expected error for only two items in relationship, got nil")
+ }
+}
+
+func TestParser2_3InvalidRelationshipTagsThreeValuesSucceed(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // three items but with interspersed additional whitespace
+ parser.rln = nil
+ err := parser.parsePair2_3("Relationship", " SPDXRef-DOCUMENT \t DESCRIBES SPDXRef-something-else ")
+ if err != nil {
+ t.Errorf("expected pass for three items in relationship w/ extra whitespace, got: %v", err)
+ }
+}
+
+func TestParser2_3InvalidRelationshipTagsFourValuesFail(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // four items
+ parser.rln = nil
+ err := parser.parsePair2_3("Relationship", "SPDXRef-a DESCRIBES SPDXRef-b SPDXRef-c")
+ if err == nil {
+ t.Errorf("expected error for more than three items in relationship, got nil")
+ }
+}
+
+func TestParser2_3InvalidRelationshipTagsInvalidRefIDs(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // four items
+ parser.rln = nil
+ err := parser.parsePair2_3("Relationship", "SPDXRef-a DESCRIBES b")
+ if err == nil {
+ t.Errorf("expected error for missing SPDXRef- prefix, got nil")
+ }
+
+ parser.rln = nil
+ err = parser.parsePair2_3("Relationship", "a DESCRIBES SPDXRef-b")
+ if err == nil {
+ t.Errorf("expected error for missing SPDXRef- prefix, got nil")
+ }
+}
+
+func TestParser2_3SpecialValuesValidForRightSideOfRelationship(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // NONE in right side of relationship should pass
+ err := parser.parsePair2_3("Relationship", "SPDXRef-a CONTAINS NONE")
+ if err != nil {
+ t.Errorf("expected nil error for CONTAINS NONE, got %v", err)
+ }
+
+ // NOASSERTION in right side of relationship should pass
+ err = parser.parsePair2_3("Relationship", "SPDXRef-a CONTAINS NOASSERTION")
+ if err != nil {
+ t.Errorf("expected nil error for CONTAINS NOASSERTION, got %v", err)
+ }
+
+ // NONE in left side of relationship should fail
+ err = parser.parsePair2_3("Relationship", "NONE CONTAINS SPDXRef-a")
+ if err == nil {
+ t.Errorf("expected non-nil error for NONE CONTAINS, got nil")
+ }
+
+ // NOASSERTION in left side of relationship should fail
+ err = parser.parsePair2_3("Relationship", "NOASSERTION CONTAINS SPDXRef-a")
+ if err == nil {
+ t.Errorf("expected non-nil error for NOASSERTION CONTAINS, got nil")
+ }
+}
+
+func TestParser2_3FailsToParseUnknownTagInRelationshipSection(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ }
+
+ // Relationship
+ err := parser.parsePair2_3("Relationship", "SPDXRef-something CONTAINS DocumentRef-otherdoc:SPDXRef-something-else")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid tag
+ err = parser.parsePairForRelationship2_3("blah", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
diff --git a/tvloader/parser2v3/parse_review.go b/tvloader/parser2v3/parse_review.go
new file mode 100644
index 0000000..c7ff99c
--- /dev/null
+++ b/tvloader/parser2v3/parse_review.go
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+func (parser *tvParser2_3) parsePairFromReview2_3(tag string, value string) error {
+ switch tag {
+ // tag for creating new review section
+ case "Reviewer":
+ parser.rev = &v2_3.Review{}
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+ subkey, subvalue, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ switch subkey {
+ case "Person":
+ parser.rev.Reviewer = subvalue
+ parser.rev.ReviewerType = "Person"
+ case "Organization":
+ parser.rev.Reviewer = subvalue
+ parser.rev.ReviewerType = "Organization"
+ case "Tool":
+ parser.rev.Reviewer = subvalue
+ parser.rev.ReviewerType = "Tool"
+ default:
+ return fmt.Errorf("unrecognized Reviewer type %v", subkey)
+ }
+ case "ReviewDate":
+ parser.rev.ReviewDate = value
+ case "ReviewComment":
+ parser.rev.ReviewComment = value
+ // for relationship tags, pass along but don't change state
+ case "Relationship":
+ parser.rln = &v2_3.Relationship{}
+ parser.doc.Relationships = append(parser.doc.Relationships, parser.rln)
+ return parser.parsePairForRelationship2_3(tag, value)
+ case "RelationshipComment":
+ return parser.parsePairForRelationship2_3(tag, value)
+ // for annotation tags, pass along but don't change state
+ case "Annotator":
+ parser.ann = &v2_3.Annotation{}
+ parser.doc.Annotations = append(parser.doc.Annotations, parser.ann)
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationDate":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationType":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "SPDXREF":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationComment":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ default:
+ return fmt.Errorf("received unknown tag %v in Review section", tag)
+ }
+
+ return nil
+}
diff --git a/tvloader/parser2v3/parse_review_test.go b/tvloader/parser2v3/parse_review_test.go
new file mode 100644
index 0000000..7026a36
--- /dev/null
+++ b/tvloader/parser2v3/parse_review_test.go
@@ -0,0 +1,414 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Parser review section state change tests =====
+func TestParser2_3ReviewStartsNewReviewAfterParsingReviewerTag(t *testing.T) {
+ // create the first review
+ rev1 := "John Doe"
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{
+ Reviewer: rev1,
+ ReviewerType: "Person",
+ },
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+ r1 := parser.rev
+
+ // the Document's Reviews should have this one only
+ if len(parser.doc.Reviews) != 1 {
+ t.Errorf("Expected only one review, got %d", len(parser.doc.Reviews))
+ }
+ if parser.doc.Reviews[0] != r1 {
+ t.Errorf("Expected review %v in Reviews[0], got %v", r1, parser.doc.Reviews[0])
+ }
+ if parser.doc.Reviews[0].Reviewer != rev1 {
+ t.Errorf("expected review name %s in Reviews[0], got %s", rev1, parser.doc.Reviews[0].Reviewer)
+ }
+
+ // now add a new review
+ rev2 := "Steve"
+ rp2 := "Person: Steve"
+ err := parser.parsePair2_3("Reviewer", rp2)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+ // and a review should be created
+ if parser.rev == nil {
+ t.Fatalf("parser didn't create new review")
+ }
+ // and the reviewer's name should be as expected
+ if parser.rev.Reviewer != rev2 {
+ t.Errorf("expected reviewer name %s, got %s", rev2, parser.rev.Reviewer)
+ }
+ // and the Document's reviews should be of size 2 and have these two
+ if len(parser.doc.Reviews) != 2 {
+ t.Fatalf("Expected Reviews to have len 2, got %d", len(parser.doc.Reviews))
+ }
+ if parser.doc.Reviews[0] != r1 {
+ t.Errorf("Expected review %v in Reviews[0], got %v", r1, parser.doc.Reviews[0])
+ }
+ if parser.doc.Reviews[0].Reviewer != rev1 {
+ t.Errorf("expected reviewer name %s in Reviews[0], got %s", rev1, parser.doc.Reviews[0].Reviewer)
+ }
+ if parser.doc.Reviews[1] != parser.rev {
+ t.Errorf("Expected review %v in Reviews[1], got %v", parser.rev, parser.doc.Reviews[1])
+ }
+ if parser.doc.Reviews[1].Reviewer != rev2 {
+ t.Errorf("expected reviewer name %s in Reviews[1], got %s", rev2, parser.doc.Reviews[1].Reviewer)
+ }
+
+}
+
+func TestParser2_3ReviewStaysAfterParsingRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{
+ Reviewer: "Jane Doe",
+ ReviewerType: "Person",
+ },
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-else")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should remain unchanged
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+ // and the relationship should be in the Document's Relationships
+ if len(parser.doc.Relationships) != 1 {
+ t.Fatalf("expected doc.Relationships to have len 1, got %d", len(parser.doc.Relationships))
+ }
+ deID := parser.doc.Relationships[0].RefA
+ if deID.DocumentRefID != "" || deID.ElementRefID != "blah" {
+ t.Errorf("expected RefA to be %s, got %s", "blah", parser.doc.Relationships[0].RefA)
+ }
+
+ err = parser.parsePair2_3("RelationshipComment", "blah")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should still remain unchanged
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+}
+
+func TestParser2_3ReviewStaysAfterParsingAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{
+ Reviewer: "Jane Doe",
+ ReviewerType: "Person",
+ },
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psReview2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationDate", "2018-09-15T00:36:00Z")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psReview2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psReview2_3)
+ }
+
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psReview2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationComment", "i guess i had something to say about this particular file")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psReview2_3)
+ }
+
+ // and the annotation should be in the Document's Annotations
+ if len(parser.doc.Annotations) != 1 {
+ t.Fatalf("expected doc.Annotations to have len 1, got %d", len(parser.doc.Annotations))
+ }
+ if parser.doc.Annotations[0].Annotator.Annotator != "John Doe ()" {
+ t.Errorf("expected Annotator to be %s, got %s", "John Doe ()", parser.doc.Annotations[0].Annotator)
+ }
+}
+
+func TestParser2_3ReviewFailsAfterParsingOtherSectionTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ // can't go back to old sections
+ err := parser.parsePair2_3("SPDXVersion", "SPDX-2.3")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+ err = parser.parsePair2_3("PackageName", "whatever")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+ err = parser.parsePair2_3("FileName", "whatever")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+ err = parser.parsePair2_3("LicenseID", "LicenseRef-Lic22")
+ if err == nil {
+ t.Errorf("expected error when calling parsePair2_3, got nil")
+ }
+}
+
+// ===== Review data section tests =====
+func TestParser2_3CanParseReviewTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ // Reviewer (DEPRECATED)
+ // handled in subsequent subtests
+
+ // Review Date (DEPRECATED)
+ err := parser.parsePairFromReview2_3("ReviewDate", "2018-09-23T08:30:00Z")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rev.ReviewDate != "2018-09-23T08:30:00Z" {
+ t.Errorf("got %v for ReviewDate", parser.rev.ReviewDate)
+ }
+
+ // Review Comment (DEPRECATED)
+ err = parser.parsePairFromReview2_3("ReviewComment", "this is a comment")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rev.ReviewComment != "this is a comment" {
+ t.Errorf("got %v for ReviewComment", parser.rev.ReviewComment)
+ }
+}
+
+func TestParser2_3CanParseReviewerPersonTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ // Reviewer: Person
+ err := parser.parsePairFromReview2_3("Reviewer", "Person: John Doe")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rev.Reviewer != "John Doe" {
+ t.Errorf("got %v for Reviewer", parser.rev.Reviewer)
+ }
+ if parser.rev.ReviewerType != "Person" {
+ t.Errorf("got %v for ReviewerType", parser.rev.ReviewerType)
+ }
+}
+
+func TestParser2_3CanParseReviewerOrganizationTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ // Reviewer: Organization
+ err := parser.parsePairFromReview2_3("Reviewer", "Organization: John Doe, Inc.")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rev.Reviewer != "John Doe, Inc." {
+ t.Errorf("got %v for Reviewer", parser.rev.Reviewer)
+ }
+ if parser.rev.ReviewerType != "Organization" {
+ t.Errorf("got %v for ReviewerType", parser.rev.ReviewerType)
+ }
+}
+
+func TestParser2_3CanParseReviewerToolTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ // Reviewer: Tool
+ err := parser.parsePairFromReview2_3("Reviewer", "Tool: scannertool - 1.2.12")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.rev.Reviewer != "scannertool - 1.2.12" {
+ t.Errorf("got %v for Reviewer", parser.rev.Reviewer)
+ }
+ if parser.rev.ReviewerType != "Tool" {
+ t.Errorf("got %v for ReviewerType", parser.rev.ReviewerType)
+ }
+}
+
+func TestParser2_3FailsIfReviewerInvalidFormat(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ err := parser.parsePairFromReview2_3("Reviewer", "oops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsIfReviewerUnknownType(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ err := parser.parsePairFromReview2_3("Reviewer", "whoops: John Doe")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3ReviewUnknownTagFails(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psReview2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1"},
+ otherLic: &v2_3.OtherLicense{
+ LicenseIdentifier: "LicenseRef-Lic11",
+ LicenseName: "License 11",
+ },
+ rev: &v2_3.Review{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.doc.OtherLicenses = append(parser.doc.OtherLicenses, parser.otherLic)
+ parser.doc.Reviews = append(parser.doc.Reviews, parser.rev)
+
+ err := parser.parsePairFromReview2_3("blah", "something")
+ if err == nil {
+ t.Errorf("expected error from parsing unknown tag")
+ }
+}
diff --git a/tvloader/parser2v3/parse_snippet.go b/tvloader/parser2v3/parse_snippet.go
new file mode 100644
index 0000000..d718dd0
--- /dev/null
+++ b/tvloader/parser2v3/parse_snippet.go
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+func (parser *tvParser2_3) parsePairFromSnippet2_3(tag string, value string) error {
+ switch tag {
+ // tag for creating new snippet section
+ case "SnippetSPDXID":
+ // check here whether the file contained an SPDX ID or not
+ if parser.file != nil && parser.file.FileSPDXIdentifier == nullSpdxElementId2_3 {
+ return fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName)
+ }
+ parser.snippet = &v2_3.Snippet{}
+ eID, err := extractElementID(value)
+ if err != nil {
+ return err
+ }
+ // FIXME: how should we handle where not associated with current file?
+ if parser.file != nil {
+ if parser.file.Snippets == nil {
+ parser.file.Snippets = map[common.ElementID]*v2_3.Snippet{}
+ }
+ parser.file.Snippets[eID] = parser.snippet
+ }
+ parser.snippet.SnippetSPDXIdentifier = eID
+ // tag for creating new file section and going back to parsing File
+ case "FileName":
+ parser.st = psFile2_3
+ parser.snippet = nil
+ return parser.parsePairFromFile2_3(tag, value)
+ // tag for creating new package section and going back to parsing Package
+ case "PackageName":
+ parser.st = psPackage2_3
+ parser.file = nil
+ parser.snippet = nil
+ return parser.parsePairFromPackage2_3(tag, value)
+ // tag for going on to other license section
+ case "LicenseID":
+ parser.st = psOtherLicense2_3
+ return parser.parsePairFromOtherLicense2_3(tag, value)
+ // tags for snippet data
+ case "SnippetFromFileSPDXID":
+ deID, err := extractDocElementID(value)
+ if err != nil {
+ return err
+ }
+ parser.snippet.SnippetFromFileSPDXIdentifier = deID.ElementRefID
+ case "SnippetByteRange":
+ byteStart, byteEnd, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ bIntStart, err := strconv.Atoi(byteStart)
+ if err != nil {
+ return err
+ }
+ bIntEnd, err := strconv.Atoi(byteEnd)
+ if err != nil {
+ return err
+ }
+
+ if parser.snippet.Ranges == nil {
+ parser.snippet.Ranges = []common.SnippetRange{}
+ }
+ byteRange := common.SnippetRange{StartPointer: common.SnippetRangePointer{Offset: bIntStart}, EndPointer: common.SnippetRangePointer{Offset: bIntEnd}}
+ parser.snippet.Ranges = append(parser.snippet.Ranges, byteRange)
+ case "SnippetLineRange":
+ lineStart, lineEnd, err := extractSubs(value)
+ if err != nil {
+ return err
+ }
+ lInttStart, err := strconv.Atoi(lineStart)
+ if err != nil {
+ return err
+ }
+ lInttEnd, err := strconv.Atoi(lineEnd)
+ if err != nil {
+ return err
+ }
+
+ if parser.snippet.Ranges == nil {
+ parser.snippet.Ranges = []common.SnippetRange{}
+ }
+ lineRange := common.SnippetRange{StartPointer: common.SnippetRangePointer{LineNumber: lInttStart}, EndPointer: common.SnippetRangePointer{LineNumber: lInttEnd}}
+ parser.snippet.Ranges = append(parser.snippet.Ranges, lineRange)
+ case "SnippetLicenseConcluded":
+ parser.snippet.SnippetLicenseConcluded = value
+ case "LicenseInfoInSnippet":
+ parser.snippet.LicenseInfoInSnippet = append(parser.snippet.LicenseInfoInSnippet, value)
+ case "SnippetLicenseComments":
+ parser.snippet.SnippetLicenseComments = value
+ case "SnippetCopyrightText":
+ parser.snippet.SnippetCopyrightText = value
+ case "SnippetComment":
+ parser.snippet.SnippetComment = value
+ case "SnippetName":
+ parser.snippet.SnippetName = value
+ case "SnippetAttributionText":
+ parser.snippet.SnippetAttributionTexts = append(parser.snippet.SnippetAttributionTexts, value)
+ // for relationship tags, pass along but don't change state
+ case "Relationship":
+ parser.rln = &v2_3.Relationship{}
+ parser.doc.Relationships = append(parser.doc.Relationships, parser.rln)
+ return parser.parsePairForRelationship2_3(tag, value)
+ case "RelationshipComment":
+ return parser.parsePairForRelationship2_3(tag, value)
+ // for annotation tags, pass along but don't change state
+ case "Annotator":
+ parser.ann = &v2_3.Annotation{}
+ parser.doc.Annotations = append(parser.doc.Annotations, parser.ann)
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationDate":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationType":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "SPDXREF":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ case "AnnotationComment":
+ return parser.parsePairForAnnotation2_3(tag, value)
+ // tag for going on to review section (DEPRECATED)
+ case "Reviewer":
+ parser.st = psReview2_3
+ return parser.parsePairFromReview2_3(tag, value)
+ default:
+ return fmt.Errorf("received unknown tag %v in Snippet section", tag)
+ }
+
+ return nil
+}
diff --git a/tvloader/parser2v3/parse_snippet_test.go b/tvloader/parser2v3/parse_snippet_test.go
new file mode 100644
index 0000000..362f22c
--- /dev/null
+++ b/tvloader/parser2v3/parse_snippet_test.go
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+// ===== Parser snippet section state change tests =====
+func TestParser2_3SnippetStartsNewSnippetAfterParsingSnippetSPDXIDTag(t *testing.T) {
+ // create the first snippet
+ sid1 := common.ElementID("s1")
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "test", PackageSPDXIdentifier: "test", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: sid1},
+ }
+ s1 := parser.snippet
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets[sid1] = parser.snippet
+
+ // the File's Snippets should have this one only
+ if len(parser.file.Snippets) != 1 {
+ t.Errorf("Expected len(Snippets) to be 1, got %d", len(parser.file.Snippets))
+ }
+ if parser.file.Snippets["s1"] != s1 {
+ t.Errorf("Expected snippet %v in Snippets[s1], got %v", s1, parser.file.Snippets["s1"])
+ }
+ if parser.file.Snippets["s1"].SnippetSPDXIdentifier != sid1 {
+ t.Errorf("expected snippet ID %s in Snippets[s1], got %s", sid1, parser.file.Snippets["s1"].SnippetSPDXIdentifier)
+ }
+
+ // now add a new snippet
+ err := parser.parsePair2_3("SnippetSPDXID", "SPDXRef-s2")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psSnippet2_3 {
+ t.Errorf("expected state to be %v, got %v", psSnippet2_3, parser.st)
+ }
+ // and a snippet should be created
+ if parser.snippet == nil {
+ t.Fatalf("parser didn't create new snippet")
+ }
+ // and the snippet ID should be as expected
+ if parser.snippet.SnippetSPDXIdentifier != "s2" {
+ t.Errorf("expected snippet ID %s, got %s", "s2", parser.snippet.SnippetSPDXIdentifier)
+ }
+ // and the File's Snippets should be of size 2 and have these two
+ if len(parser.file.Snippets) != 2 {
+ t.Errorf("Expected len(Snippets) to be 2, got %d", len(parser.file.Snippets))
+ }
+ if parser.file.Snippets["s1"] != s1 {
+ t.Errorf("Expected snippet %v in Snippets[s1], got %v", s1, parser.file.Snippets["s1"])
+ }
+ if parser.file.Snippets["s1"].SnippetSPDXIdentifier != sid1 {
+ t.Errorf("expected snippet ID %s in Snippets[s1], got %s", sid1, parser.file.Snippets["s1"].SnippetSPDXIdentifier)
+ }
+ if parser.file.Snippets["s2"] != parser.snippet {
+ t.Errorf("Expected snippet %v in Snippets[s2], got %v", parser.snippet, parser.file.Snippets["s2"])
+ }
+ if parser.file.Snippets["s2"].SnippetSPDXIdentifier != "s2" {
+ t.Errorf("expected snippet ID %s in Snippets[s2], got %s", "s2", parser.file.Snippets["s2"].SnippetSPDXIdentifier)
+ }
+}
+
+func TestParser2_3SnippetStartsNewPackageAfterParsingPackageNameTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ p1 := parser.pkg
+ f1 := parser.file
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets["s1"] = parser.snippet
+
+ // now add a new package
+ p2Name := "package2"
+ err := parser.parsePair2_3("PackageName", p2Name)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should go back to Package
+ if parser.st != psPackage2_3 {
+ t.Errorf("expected state to be %v, got %v", psPackage2_3, parser.st)
+ }
+ // and a package should be created
+ if parser.pkg == nil {
+ t.Fatalf("parser didn't create new pkg")
+ }
+ // and the package name should be as expected
+ if parser.pkg.PackageName != p2Name {
+ t.Errorf("expected package name %s, got %s", p2Name, parser.pkg.PackageName)
+ }
+ // and the package should default to true for FilesAnalyzed
+ if parser.pkg.FilesAnalyzed != true {
+ t.Errorf("expected FilesAnalyzed to default to true, got false")
+ }
+ if parser.pkg.IsFilesAnalyzedTagPresent != false {
+ t.Errorf("expected IsFilesAnalyzedTagPresent to default to false, got true")
+ }
+ // and the Document's Packages should still be of size 1 b/c no SPDX
+ // identifier has been seen yet
+ if len(parser.doc.Packages) != 1 {
+ t.Errorf("Expected len(Packages) to be 1, got %d", len(parser.doc.Packages))
+ }
+ if parser.doc.Packages[0] != p1 {
+ t.Errorf("Expected package %v in Packages[package1], got %v", p1, parser.doc.Packages[0])
+ }
+ if parser.doc.Packages[0].PackageName != "package1" {
+ t.Errorf("expected package name %s in Packages[package1], got %s", "package1", parser.doc.Packages[0].PackageName)
+ }
+ // and the first Package's Files should be of size 1 and have f1 only
+ if len(parser.doc.Packages[0].Files) != 1 {
+ t.Errorf("Expected 1 file in Packages[package1].Files, got %d", len(parser.doc.Packages[0].Files))
+ }
+ if parser.doc.Packages[0].Files[0] != f1 {
+ t.Errorf("Expected file %v in Files[f1], got %v", f1, parser.doc.Packages[0].Files[0])
+ }
+ if parser.doc.Packages[0].Files[0].FileName != "f1.txt" {
+ t.Errorf("expected file name %s in Files[f1], got %s", "f1.txt", parser.doc.Packages[0].Files[0].FileName)
+ }
+ // and the new Package should have no files
+ if len(parser.pkg.Files) != 0 {
+ t.Errorf("Expected no files in Packages[1].Files, got %d", len(parser.pkg.Files))
+ }
+ // and the current file should be nil
+ if parser.file != nil {
+ t.Errorf("Expected nil for parser.file, got %v", parser.file)
+ }
+ // and the current snippet should be nil
+ if parser.snippet != nil {
+ t.Errorf("Expected nil for parser.snippet, got %v", parser.snippet)
+ }
+}
+
+func TestParser2_3SnippetMovesToFileAfterParsingFileNameTag(t *testing.T) {
+ f1Name := "f1.txt"
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ p1 := parser.pkg
+ f1 := parser.file
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets["s1"] = parser.snippet
+
+ f2Name := "f2.txt"
+ err := parser.parsePair2_3("FileName", f2Name)
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should be correct
+ if parser.st != psFile2_3 {
+ t.Errorf("expected state to be %v, got %v", psSnippet2_3, parser.st)
+ }
+ // and current package should remain what it was
+ if parser.pkg != p1 {
+ t.Fatalf("expected package to remain %v, got %v", p1, parser.pkg)
+ }
+ // and a file should be created
+ if parser.file == nil {
+ t.Fatalf("parser didn't create new file")
+ }
+ // and the file name should be as expected
+ if parser.file.FileName != f2Name {
+ t.Errorf("expected file name %s, got %s", f2Name, parser.file.FileName)
+ }
+ // and the Package's Files should still be of size 1 since we haven't seen
+ // an SPDX identifier yet for this new file
+ if len(parser.pkg.Files) != 1 {
+ t.Errorf("Expected len(Files) to be 1, got %d", len(parser.pkg.Files))
+ }
+ if parser.pkg.Files[0] != f1 {
+ t.Errorf("Expected file %v in Files[f1], got %v", f1, parser.pkg.Files[0])
+ }
+ if parser.pkg.Files[0].FileName != f1Name {
+ t.Errorf("expected file name %s in Files[f1], got %s", f1Name, parser.pkg.Files[0].FileName)
+ }
+ // and the current snippet should be nil
+ if parser.snippet != nil {
+ t.Errorf("Expected nil for parser.snippet, got %v", parser.snippet)
+ }
+}
+
+func TestParser2_3SnippetMovesToOtherLicenseAfterParsingLicenseIDTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets["s1"] = parser.snippet
+
+ err := parser.parsePair2_3("LicenseID", "LicenseRef-TestLic")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psOtherLicense2_3 {
+ t.Errorf("expected state to be %v, got %v", psOtherLicense2_3, parser.st)
+ }
+}
+
+func TestParser2_3SnippetMovesToReviewAfterParsingReviewerTag(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets["s1"] = parser.snippet
+
+ err := parser.parsePair2_3("Reviewer", "Person: John Doe")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psReview2_3 {
+ t.Errorf("expected state to be %v, got %v", psReview2_3, parser.st)
+ }
+}
+
+func TestParser2_3SnippetStaysAfterParsingRelationshipTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets["s1"] = parser.snippet
+
+ err := parser.parsePair2_3("Relationship", "SPDXRef-blah CONTAINS SPDXRef-blah-else")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should remain unchanged
+ if parser.st != psSnippet2_3 {
+ t.Errorf("expected state to be %v, got %v", psSnippet2_3, parser.st)
+ }
+ // and the relationship should be in the Document's Relationships
+ if len(parser.doc.Relationships) != 1 {
+ t.Fatalf("expected doc.Relationships to have len 1, got %d", len(parser.doc.Relationships))
+ }
+ deID := parser.doc.Relationships[0].RefA
+ if deID.DocumentRefID != "" || deID.ElementRefID != "blah" {
+ t.Errorf("expected RefA to be %s, got %s", "blah", parser.doc.Relationships[0].RefA)
+ }
+
+ err = parser.parsePair2_3("RelationshipComment", "blah")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ // state should still remain unchanged
+ if parser.st != psSnippet2_3 {
+ t.Errorf("expected state to be %v, got %v", psSnippet2_3, parser.st)
+ }
+}
+
+func TestParser2_3SnippetStaysAfterParsingAnnotationTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+ parser.file.Snippets["s1"] = parser.snippet
+
+ err := parser.parsePair2_3("Annotator", "Person: John Doe ()")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psSnippet2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psSnippet2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationDate", "2018-09-15T00:36:00Z")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psSnippet2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psSnippet2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationType", "REVIEW")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psSnippet2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psSnippet2_3)
+ }
+
+ err = parser.parsePair2_3("SPDXREF", "SPDXRef-45")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psSnippet2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psSnippet2_3)
+ }
+
+ err = parser.parsePair2_3("AnnotationComment", "i guess i had something to say about this particular file")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.st != psSnippet2_3 {
+ t.Errorf("parser is in state %v, expected %v", parser.st, psSnippet2_3)
+ }
+
+ // and the annotation should be in the Document's Annotations
+ if len(parser.doc.Annotations) != 1 {
+ t.Fatalf("expected doc.Annotations to have len 1, got %d", len(parser.doc.Annotations))
+ }
+ if parser.doc.Annotations[0].Annotator.Annotator != "John Doe ()" {
+ t.Errorf("expected Annotator to be %s, got %s", "John Doe ()", parser.doc.Annotations[0].Annotator)
+ }
+}
+
+// ===== Snippet data section tests =====
+func TestParser2_3CanParseSnippetTags(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ // Snippet SPDX Identifier
+ err := parser.parsePairFromSnippet2_3("SnippetSPDXID", "SPDXRef-s1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.SnippetSPDXIdentifier != "s1" {
+ t.Errorf("got %v for SnippetSPDXIdentifier", parser.snippet.SnippetSPDXIdentifier)
+ }
+
+ // Snippet from File SPDX Identifier
+ err = parser.parsePairFromSnippet2_3("SnippetFromFileSPDXID", "SPDXRef-f1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ wantDeID := common.DocElementID{DocumentRefID: "", ElementRefID: common.ElementID("f1")}
+ if parser.snippet.SnippetFromFileSPDXIdentifier != wantDeID.ElementRefID {
+ t.Errorf("got %v for SnippetFromFileSPDXIdentifier", parser.snippet.SnippetFromFileSPDXIdentifier)
+ }
+
+ // Snippet Byte Range
+ err = parser.parsePairFromSnippet2_3("SnippetByteRange", "20:320")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.Ranges[0].StartPointer.Offset != 20 {
+ t.Errorf("got %v for SnippetByteRangeStart", parser.snippet.Ranges[0].StartPointer.Offset)
+ }
+ if parser.snippet.Ranges[0].EndPointer.Offset != 320 {
+ t.Errorf("got %v for SnippetByteRangeEnd", parser.snippet.Ranges[0].EndPointer.Offset)
+ }
+
+ // Snippet Line Range
+ err = parser.parsePairFromSnippet2_3("SnippetLineRange", "5:12")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.Ranges[1].StartPointer.LineNumber != 5 {
+ t.Errorf("got %v for SnippetLineRangeStart", parser.snippet.Ranges[1].StartPointer.LineNumber)
+ }
+ if parser.snippet.Ranges[1].EndPointer.LineNumber != 12 {
+ t.Errorf("got %v for SnippetLineRangeEnd", parser.snippet.Ranges[1].EndPointer.LineNumber)
+ }
+
+ // Snippet Concluded License
+ err = parser.parsePairFromSnippet2_3("SnippetLicenseConcluded", "BSD-3-Clause")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.SnippetLicenseConcluded != "BSD-3-Clause" {
+ t.Errorf("got %v for SnippetLicenseConcluded", parser.snippet.SnippetLicenseConcluded)
+ }
+
+ // License Information in Snippet
+ lics := []string{
+ "Apache-2.0",
+ "GPL-2.0-or-later",
+ "CC0-1.0",
+ }
+ for _, lic := range lics {
+ err = parser.parsePairFromSnippet2_3("LicenseInfoInSnippet", lic)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, licWant := range lics {
+ flagFound := false
+ for _, licCheck := range parser.snippet.LicenseInfoInSnippet {
+ if licWant == licCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in LicenseInfoInSnippet", licWant)
+ }
+ }
+ if len(lics) != len(parser.snippet.LicenseInfoInSnippet) {
+ t.Errorf("expected %d licenses in LicenseInfoInSnippet, got %d", len(lics),
+ len(parser.snippet.LicenseInfoInSnippet))
+ }
+
+ // Snippet Comments on License
+ err = parser.parsePairFromSnippet2_3("SnippetLicenseComments", "this is a comment about the licenses")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.SnippetLicenseComments != "this is a comment about the licenses" {
+ t.Errorf("got %v for SnippetLicenseComments", parser.snippet.SnippetLicenseComments)
+ }
+
+ // Snippet Copyright Text
+ err = parser.parsePairFromSnippet2_3("SnippetCopyrightText", "copyright (c) John Doe and friends")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.SnippetCopyrightText != "copyright (c) John Doe and friends" {
+ t.Errorf("got %v for SnippetCopyrightText", parser.snippet.SnippetCopyrightText)
+ }
+
+ // Snippet Comment
+ err = parser.parsePairFromSnippet2_3("SnippetComment", "this is a comment about the snippet")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.SnippetComment != "this is a comment about the snippet" {
+ t.Errorf("got %v for SnippetComment", parser.snippet.SnippetComment)
+ }
+
+ // Snippet Name
+ err = parser.parsePairFromSnippet2_3("SnippetName", "from some other package called abc")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ if parser.snippet.SnippetName != "from some other package called abc" {
+ t.Errorf("got %v for SnippetName", parser.snippet.SnippetName)
+ }
+
+ // Snippet Attribution Texts
+ attrs := []string{
+ "Include this notice in all advertising materials",
+ "This is a \nmulti-line string",
+ }
+ for _, attr := range attrs {
+ err = parser.parsePairFromSnippet2_3("SnippetAttributionText", attr)
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ }
+ for _, attrWant := range attrs {
+ flagFound := false
+ for _, attrCheck := range parser.snippet.SnippetAttributionTexts {
+ if attrWant == attrCheck {
+ flagFound = true
+ }
+ }
+ if flagFound == false {
+ t.Errorf("didn't find %s in SnippetAttributionText", attrWant)
+ }
+ }
+ if len(attrs) != len(parser.snippet.SnippetAttributionTexts) {
+ t.Errorf("expected %d attribution texts in SnippetAttributionTexts, got %d", len(attrs),
+ len(parser.snippet.SnippetAttributionTexts))
+ }
+
+}
+
+func TestParser2_3SnippetUnknownTagFails(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{SnippetSPDXIdentifier: "s1"},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ err := parser.parsePairFromSnippet2_3("blah", "something")
+ if err == nil {
+ t.Errorf("expected error from parsing unknown tag")
+ }
+}
+
+func TestParser2_3FailsForInvalidSnippetSPDXID(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ // invalid Snippet SPDX Identifier
+ err := parser.parsePairFromSnippet2_3("SnippetSPDXID", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsForInvalidSnippetFromFileSPDXID(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ // start with Snippet SPDX Identifier
+ err := parser.parsePairFromSnippet2_3("SnippetSPDXID", "SPDXRef-s1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid From File identifier
+ err = parser.parsePairFromSnippet2_3("SnippetFromFileSPDXID", "whoops")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsForInvalidSnippetByteValues(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ // start with Snippet SPDX Identifier
+ err := parser.parsePairFromSnippet2_3("SnippetSPDXID", "SPDXRef-s1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid byte formats and values
+ err = parser.parsePairFromSnippet2_3("SnippetByteRange", "200 210")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+ err = parser.parsePairFromSnippet2_3("SnippetByteRange", "a:210")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+ err = parser.parsePairFromSnippet2_3("SnippetByteRange", "200:a")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FailsForInvalidSnippetLineValues(t *testing.T) {
+ parser := tvParser2_3{
+ doc: &v2_3.Document{Packages: []*v2_3.Package{}},
+ st: psSnippet2_3,
+ pkg: &v2_3.Package{PackageName: "package1", PackageSPDXIdentifier: "package1", Files: []*v2_3.File{}},
+ file: &v2_3.File{FileName: "f1.txt", FileSPDXIdentifier: "f1", Snippets: map[common.ElementID]*v2_3.Snippet{}},
+ snippet: &v2_3.Snippet{},
+ }
+ parser.doc.Packages = append(parser.doc.Packages, parser.pkg)
+ parser.pkg.Files = append(parser.pkg.Files, parser.file)
+
+ // start with Snippet SPDX Identifier
+ err := parser.parsePairFromSnippet2_3("SnippetSPDXID", "SPDXRef-s1")
+ if err != nil {
+ t.Errorf("expected nil error, got %v", err)
+ }
+ // invalid byte formats and values
+ err = parser.parsePairFromSnippet2_3("SnippetLineRange", "200 210")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+ err = parser.parsePairFromSnippet2_3("SnippetLineRange", "a:210")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+ err = parser.parsePairFromSnippet2_3("SnippetLineRange", "200:a")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FilesWithoutSpdxIdThrowErrorWithSnippets(t *testing.T) {
+ // Invalid file with snippet
+ // Last unpackaged file before the snippet starts
+ // Last file of a package and New package starts
+ fileName := "f2.txt"
+ sid1 := common.ElementID("s1")
+ parser2 := tvParser2_3{
+ doc: &v2_3.Document{},
+ st: psCreationInfo2_3,
+ file: &v2_3.File{FileName: fileName},
+ }
+ err := parser2.parsePair2_3("SnippetSPDXID", string(sid1))
+ if err == nil {
+ t.Errorf("file without SPDX Identifier getting accepted")
+ }
+
+}
diff --git a/tvloader/parser2v3/parser.go b/tvloader/parser2v3/parser.go
new file mode 100644
index 0000000..af84d93
--- /dev/null
+++ b/tvloader/parser2v3/parser.go
@@ -0,0 +1,100 @@
+// Package parser2v3 contains functions to read, load and parse
+// SPDX tag-value files, version 2.3.
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "fmt"
+
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+ "github.com/spdx/tools-golang/tvloader/reader"
+)
+
+// ParseTagValues takes a list of (tag, value) pairs, parses it and returns
+// a pointer to a parsed SPDX Document.
+func ParseTagValues(tvs []reader.TagValuePair) (*v2_3.Document, error) {
+ parser := tvParser2_3{}
+ for _, tv := range tvs {
+ err := parser.parsePair2_3(tv.Tag, tv.Value)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if parser.file != nil && parser.file.FileSPDXIdentifier == nullSpdxElementId2_3 {
+ return nil, fmt.Errorf("file with FileName %s does not have SPDX identifier", parser.file.FileName)
+ }
+ if parser.pkg != nil && parser.pkg.PackageSPDXIdentifier == nullSpdxElementId2_3 {
+ return nil, fmt.Errorf("package with PackageName %s does not have SPDX identifier", parser.pkg.PackageName)
+ }
+ return parser.doc, nil
+}
+
+func (parser *tvParser2_3) parsePair2_3(tag string, value string) error {
+ switch parser.st {
+ case psStart2_3:
+ return parser.parsePairFromStart2_3(tag, value)
+ case psCreationInfo2_3:
+ return parser.parsePairFromCreationInfo2_3(tag, value)
+ case psPackage2_3:
+ return parser.parsePairFromPackage2_3(tag, value)
+ case psFile2_3:
+ return parser.parsePairFromFile2_3(tag, value)
+ case psSnippet2_3:
+ return parser.parsePairFromSnippet2_3(tag, value)
+ case psOtherLicense2_3:
+ return parser.parsePairFromOtherLicense2_3(tag, value)
+ case psReview2_3:
+ return parser.parsePairFromReview2_3(tag, value)
+ default:
+ return fmt.Errorf("parser state %v not recognized when parsing (%s, %s)", parser.st, tag, value)
+ }
+}
+
+func (parser *tvParser2_3) parsePairFromStart2_3(tag string, value string) error {
+ // fail if not in Start parser state
+ if parser.st != psStart2_3 {
+ return fmt.Errorf("got invalid state %v in parsePairFromStart2_3", parser.st)
+ }
+
+ // create an SPDX Document data struct if we don't have one already
+ if parser.doc == nil {
+ parser.doc = &v2_3.Document{ExternalDocumentReferences: []v2_3.ExternalDocumentRef{}}
+ }
+
+ switch tag {
+ case "DocumentComment":
+ parser.doc.DocumentComment = value
+ case "SPDXVersion":
+ parser.doc.SPDXVersion = value
+ case "DataLicense":
+ parser.doc.DataLicense = value
+ case "SPDXID":
+ eID, err := extractElementID(value)
+ if err != nil {
+ return err
+ }
+ parser.doc.SPDXIdentifier = eID
+ case "DocumentName":
+ parser.doc.DocumentName = value
+ case "DocumentNamespace":
+ parser.doc.DocumentNamespace = value
+ case "ExternalDocumentRef":
+ documentRefID, uri, alg, checksum, err := extractExternalDocumentReference(value)
+ if err != nil {
+ return err
+ }
+ edr := v2_3.ExternalDocumentRef{
+ DocumentRefID: documentRefID,
+ URI: uri,
+ Checksum: common.Checksum{Algorithm: common.ChecksumAlgorithm(alg), Value: checksum},
+ }
+ parser.doc.ExternalDocumentReferences = append(parser.doc.ExternalDocumentReferences, edr)
+ default:
+ // move to Creation Info parser state
+ parser.st = psCreationInfo2_3
+ return parser.parsePairFromCreationInfo2_3(tag, value)
+ }
+
+ return nil
+}
diff --git a/tvloader/parser2v3/parser_test.go b/tvloader/parser2v3/parser_test.go
new file mode 100644
index 0000000..dfd8c04
--- /dev/null
+++ b/tvloader/parser2v3/parser_test.go
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/tvloader/reader"
+)
+
+// ===== Parser exported entry point tests =====
+func TestParser2_3CanParseTagValues(t *testing.T) {
+ var tvPairs []reader.TagValuePair
+
+ // create some pairs
+ tvPair1 := reader.TagValuePair{Tag: "SPDXVersion", Value: "SPDX-2.3"}
+ tvPairs = append(tvPairs, tvPair1)
+ tvPair2 := reader.TagValuePair{Tag: "DataLicense", Value: "CC0-1.0"}
+ tvPairs = append(tvPairs, tvPair2)
+ tvPair3 := reader.TagValuePair{Tag: "SPDXID", Value: "SPDXRef-DOCUMENT"}
+ tvPairs = append(tvPairs, tvPair3)
+
+ // now parse them
+ doc, err := ParseTagValues(tvPairs)
+ if err != nil {
+ t.Errorf("got error when calling ParseTagValues: %v", err)
+ }
+ if doc.SPDXVersion != "SPDX-2.3" {
+ t.Errorf("expected SPDXVersion to be SPDX-2.3, got %v", doc.SPDXVersion)
+ }
+ if doc.DataLicense != "CC0-1.0" {
+ t.Errorf("expected DataLicense to be CC0-1.0, got %v", doc.DataLicense)
+ }
+ if doc.SPDXIdentifier != "DOCUMENT" {
+ t.Errorf("expected SPDXIdentifier to be DOCUMENT, got %v", doc.SPDXIdentifier)
+ }
+
+}
+
+// ===== Parser initialization tests =====
+func TestParser2_3InitCreatesResetStatus(t *testing.T) {
+ parser := tvParser2_3{}
+ if parser.st != psStart2_3 {
+ t.Errorf("parser did not begin in start state")
+ }
+ if parser.doc != nil {
+ t.Errorf("parser did not begin with nil document")
+ }
+}
+
+func TestParser2_3HasDocumentAfterCallToParseFirstTag(t *testing.T) {
+ parser := tvParser2_3{}
+ err := parser.parsePair2_3("SPDXVersion", "SPDX-2.3")
+ if err != nil {
+ t.Errorf("got error when calling parsePair2_3: %v", err)
+ }
+ if parser.doc == nil {
+ t.Errorf("doc is still nil after parsing first pair")
+ }
+}
+
+func TestParser2_3StartFailsToParseIfInInvalidState(t *testing.T) {
+ parser := tvParser2_3{st: psReview2_3}
+ err := parser.parsePairFromStart2_3("SPDXVersion", "SPDX-2.3")
+ if err == nil {
+ t.Errorf("expected non-nil error, got nil")
+ }
+}
+
+func TestParser2_3FilesWithoutSpdxIdThrowErrorAtCompleteParse(t *testing.T) {
+ // case: Checks the last file
+ // Last unpackaged file with no packages in doc
+ // Last file of last package in the doc
+ tvPairs := []reader.TagValuePair{
+ {Tag: "SPDXVersion", Value: "SPDX-2.3"},
+ {Tag: "DataLicense", Value: "CC0-1.0"},
+ {Tag: "SPDXID", Value: "SPDXRef-DOCUMENT"},
+ {Tag: "FileName", Value: "f1"},
+ }
+ _, err := ParseTagValues(tvPairs)
+ if err == nil {
+ t.Errorf("file without SPDX Identifier getting accepted")
+ }
+}
+
+func TestParser2_3PackageWithoutSpdxIdThrowErrorAtCompleteParse(t *testing.T) {
+ // case: Checks the last package
+ tvPairs := []reader.TagValuePair{
+ {Tag: "SPDXVersion", Value: "SPDX-2.3"},
+ {Tag: "DataLicense", Value: "CC0-1.0"},
+ {Tag: "SPDXID", Value: "SPDXRef-DOCUMENT"},
+ {Tag: "PackageName", Value: "p1"},
+ }
+ _, err := ParseTagValues(tvPairs)
+ if err == nil {
+ t.Errorf("package without SPDX Identifier getting accepted")
+ }
+}
diff --git a/tvloader/parser2v3/types.go b/tvloader/parser2v3/types.go
new file mode 100644
index 0000000..b6dc3ab
--- /dev/null
+++ b/tvloader/parser2v3/types.go
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "github.com/spdx/tools-golang/spdx/common"
+ "github.com/spdx/tools-golang/spdx/v2_3"
+)
+
+type tvParser2_3 struct {
+ // document into which data is being parsed
+ doc *v2_3.Document
+
+ // current parser state
+ st tvParserState2_3
+
+ // current SPDX item being filled in, if any
+ pkg *v2_3.Package
+ pkgExtRef *v2_3.PackageExternalReference
+ file *v2_3.File
+ fileAOP *v2_3.ArtifactOfProject
+ snippet *v2_3.Snippet
+ otherLic *v2_3.OtherLicense
+ rln *v2_3.Relationship
+ ann *v2_3.Annotation
+ rev *v2_3.Review
+ // don't need creation info pointer b/c only one,
+ // and we can get to it via doc.CreationInfo
+}
+
+// parser state (SPDX document version 2.3)
+type tvParserState2_3 int
+
+const (
+ // at beginning of document
+ psStart2_3 tvParserState2_3 = iota
+
+ // in document creation info section
+ psCreationInfo2_3
+
+ // in package data section
+ psPackage2_3
+
+ // in file data section (including "unpackaged" files)
+ psFile2_3
+
+ // in snippet data section (including "unpackaged" files)
+ psSnippet2_3
+
+ // in other license section
+ psOtherLicense2_3
+
+ // in review section
+ psReview2_3
+)
+
+const nullSpdxElementId2_3 = common.ElementID("")
diff --git a/tvloader/parser2v3/util.go b/tvloader/parser2v3/util.go
new file mode 100644
index 0000000..743b3f6
--- /dev/null
+++ b/tvloader/parser2v3/util.go
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+
+package parser2v3
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/spdx/tools-golang/spdx/common"
+)
+
+// used to extract key / value from embedded substrings
+// returns subkey, subvalue, nil if no error, or "", "", error otherwise
+func extractSubs(value string) (string, string, error) {
+ // parse the value to see if it's a valid subvalue format
+ sp := strings.SplitN(value, ":", 2)
+ if len(sp) == 1 {
+ return "", "", fmt.Errorf("invalid subvalue format for %s (no colon found)", value)
+ }
+
+ subkey := strings.TrimSpace(sp[0])
+ subvalue := strings.TrimSpace(sp[1])
+
+ return subkey, subvalue, nil
+}
+
+// used to extract DocumentRef and SPDXRef values from an SPDX Identifier
+// which can point either to this document or to a different one
+func extractDocElementID(value string) (common.DocElementID, error) {
+ docRefID := ""
+ idStr := value
+
+ // check prefix to see if it's a DocumentRef ID
+ if strings.HasPrefix(idStr, "DocumentRef-") {
+ // extract the part that comes between "DocumentRef-" and ":"
+ strs := strings.Split(idStr, ":")
+ // should be exactly two, part before and part after
+ if len(strs) < 2 {
+ return common.DocElementID{}, fmt.Errorf("no colon found although DocumentRef- prefix present")
+ }
+ if len(strs) > 2 {
+ return common.DocElementID{}, fmt.Errorf("more than one colon found")
+ }
+
+ // trim the prefix and confirm non-empty
+ docRefID = strings.TrimPrefix(strs[0], "DocumentRef-")
+ if docRefID == "" {
+ return common.DocElementID{}, fmt.Errorf("document identifier has nothing after prefix")
+ }
+ // and use remainder for element ID parsing
+ idStr = strs[1]
+ }
+
+ // check prefix to confirm it's got the right prefix for element IDs
+ if !strings.HasPrefix(idStr, "SPDXRef-") {
+ return common.DocElementID{}, fmt.Errorf("missing SPDXRef- prefix for element identifier")
+ }
+
+ // make sure no colons are present
+ if strings.Contains(idStr, ":") {
+ // we know this means there was no DocumentRef- prefix, because
+ // we would have handled multiple colons above if it was
+ return common.DocElementID{}, fmt.Errorf("invalid colon in element identifier")
+ }
+
+ // trim the prefix and confirm non-empty
+ eltRefID := strings.TrimPrefix(idStr, "SPDXRef-")
+ if eltRefID == "" {
+ return common.DocElementID{}, fmt.Errorf("element identifier has nothing after prefix")
+ }
+
+ // we're good
+ return common.DocElementID{DocumentRefID: docRefID, ElementRefID: common.ElementID(eltRefID)}, nil
+}
+
+// used to extract SPDXRef values from an SPDX Identifier, OR "special" strings
+// from a specified set of permitted values. The primary use case for this is
+// the right-hand side of Relationships, where beginning in SPDX 2.3 the values
+// "NONE" and "NOASSERTION" are permitted. If the value does not match one of
+// the specified permitted values, it will fall back to the ordinary
+// DocElementID extractor.
+func extractDocElementSpecial(value string, permittedSpecial []string) (common.DocElementID, error) {
+ // check value against special set first
+ for _, sp := range permittedSpecial {
+ if sp == value {
+ return common.DocElementID{SpecialID: sp}, nil
+ }
+ }
+ // not found, fall back to regular search
+ return extractDocElementID(value)
+}
+
+// used to extract SPDXRef values only from an SPDX Identifier which can point
+// to this document only. Use extractDocElementID for parsing IDs that can
+// refer either to this document or a different one.
+func extractElementID(value string) (common.ElementID, error) {
+ // check prefix to confirm it's got the right prefix for element IDs
+ if !strings.HasPrefix(value, "SPDXRef-") {
+ return common.ElementID(""), fmt.Errorf("missing SPDXRef- prefix for element identifier")
+ }
+
+ // make sure no colons are present
+ if strings.Contains(value, ":") {
+ return common.ElementID(""), fmt.Errorf("invalid colon in element identifier")
+ }
+
+ // trim the prefix and confirm non-empty
+ eltRefID := strings.TrimPrefix(value, "SPDXRef-")
+ if eltRefID == "" {
+ return common.ElementID(""), fmt.Errorf("element identifier has nothing after prefix")
+ }
+
+ // we're good
+ return common.ElementID(eltRefID), nil
+}
diff --git a/tvloader/parser2v3/util_test.go b/tvloader/parser2v3/util_test.go
new file mode 100644
index 0000000..6269b91
--- /dev/null
+++ b/tvloader/parser2v3/util_test.go
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+package parser2v3
+
+import (
+ "testing"
+
+ "github.com/spdx/tools-golang/spdx/common"
+)
+
+// ===== Helper function tests =====
+
+func TestCanExtractSubvalues(t *testing.T) {
+ subkey, subvalue, err := extractSubs("SHA1: abc123")
+ if err != nil {
+ t.Errorf("got error when calling extractSubs: %v", err)
+ }
+ if subkey != "SHA1" {
+ t.Errorf("got %v for subkey", subkey)
+ }
+ if subvalue != "abc123" {
+ t.Errorf("got %v for subvalue", subvalue)
+ }
+}
+
+func TestReturnsErrorForInvalidSubvalueFormat(t *testing.T) {
+ _, _, err := extractSubs("blah")
+ if err == nil {
+ t.Errorf("expected error when calling extractSubs for invalid format (0 colons), got nil")
+ }
+}
+
+func TestCanExtractDocumentAndElementRefsFromID(t *testing.T) {
+ // test with valid ID in this document
+ helperForExtractDocElementID(t, "SPDXRef-file1", false, "", "file1")
+ // test with valid ID in another document
+ helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file2", false, "doc2", "file2")
+ // test with invalid ID in this document
+ helperForExtractDocElementID(t, "a:SPDXRef-file1", true, "", "")
+ helperForExtractDocElementID(t, "file1", true, "", "")
+ helperForExtractDocElementID(t, "SPDXRef-", true, "", "")
+ helperForExtractDocElementID(t, "SPDXRef-file1:", true, "", "")
+ // test with invalid ID in another document
+ helperForExtractDocElementID(t, "DocumentRef-doc2", true, "", "")
+ helperForExtractDocElementID(t, "DocumentRef-doc2:", true, "", "")
+ helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-", true, "", "")
+ helperForExtractDocElementID(t, "DocumentRef-doc2:a", true, "", "")
+ helperForExtractDocElementID(t, "DocumentRef-:", true, "", "")
+ helperForExtractDocElementID(t, "DocumentRef-:SPDXRef-file1", true, "", "")
+ // test with invalid formats
+ helperForExtractDocElementID(t, "DocumentRef-doc2:SPDXRef-file1:file2", true, "", "")
+}
+
+func helperForExtractDocElementID(t *testing.T, tst string, wantErr bool, wantDoc string, wantElt string) {
+ deID, err := extractDocElementID(tst)
+ if err != nil && wantErr == false {
+ t.Errorf("testing %v: expected nil error, got %v", tst, err)
+ }
+ if err == nil && wantErr == true {
+ t.Errorf("testing %v: expected non-nil error, got nil", tst)
+ }
+ if deID.DocumentRefID != wantDoc {
+ if wantDoc == "" {
+ t.Errorf("testing %v: want empty string for DocumentRefID, got %v", tst, deID.DocumentRefID)
+ } else {
+ t.Errorf("testing %v: want %v for DocumentRefID, got %v", tst, wantDoc, deID.DocumentRefID)
+ }
+ }
+ if deID.ElementRefID != common.ElementID(wantElt) {
+ if wantElt == "" {
+ t.Errorf("testing %v: want empty string for ElementRefID, got %v", tst, deID.ElementRefID)
+ } else {
+ t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, deID.ElementRefID)
+ }
+ }
+}
+
+func TestCanExtractSpecialDocumentIDs(t *testing.T) {
+ permittedSpecial := []string{"NONE", "NOASSERTION"}
+ // test with valid special values
+ helperForExtractDocElementSpecial(t, permittedSpecial, "NONE", false, "", "", "NONE")
+ helperForExtractDocElementSpecial(t, permittedSpecial, "NOASSERTION", false, "", "", "NOASSERTION")
+ // test with valid regular IDs
+ helperForExtractDocElementSpecial(t, permittedSpecial, "SPDXRef-file1", false, "", "file1", "")
+ helperForExtractDocElementSpecial(t, permittedSpecial, "DocumentRef-doc2:SPDXRef-file2", false, "doc2", "file2", "")
+ helperForExtractDocElementSpecial(t, permittedSpecial, "a:SPDXRef-file1", true, "", "", "")
+ helperForExtractDocElementSpecial(t, permittedSpecial, "DocumentRef-doc2", true, "", "", "")
+ // test with invalid other words not on permitted list
+ helperForExtractDocElementSpecial(t, permittedSpecial, "FOO", true, "", "", "")
+}
+
+func helperForExtractDocElementSpecial(t *testing.T, permittedSpecial []string, tst string, wantErr bool, wantDoc string, wantElt string, wantSpecial string) {
+ deID, err := extractDocElementSpecial(tst, permittedSpecial)
+ if err != nil && wantErr == false {
+ t.Errorf("testing %v: expected nil error, got %v", tst, err)
+ }
+ if err == nil && wantErr == true {
+ t.Errorf("testing %v: expected non-nil error, got nil", tst)
+ }
+ if deID.DocumentRefID != wantDoc {
+ if wantDoc == "" {
+ t.Errorf("testing %v: want empty string for DocumentRefID, got %v", tst, deID.DocumentRefID)
+ } else {
+ t.Errorf("testing %v: want %v for DocumentRefID, got %v", tst, wantDoc, deID.DocumentRefID)
+ }
+ }
+ if deID.ElementRefID != common.ElementID(wantElt) {
+ if wantElt == "" {
+ t.Errorf("testing %v: want empty string for ElementRefID, got %v", tst, deID.ElementRefID)
+ } else {
+ t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, deID.ElementRefID)
+ }
+ }
+ if deID.SpecialID != wantSpecial {
+ if wantSpecial == "" {
+ t.Errorf("testing %v: want empty string for SpecialID, got %v", tst, deID.SpecialID)
+ } else {
+ t.Errorf("testing %v: want %v for SpecialID, got %v", tst, wantSpecial, deID.SpecialID)
+ }
+ }
+}
+
+func TestCanExtractElementRefsOnlyFromID(t *testing.T) {
+ // test with valid ID in this document
+ helperForExtractElementID(t, "SPDXRef-file1", false, "file1")
+ // test with valid ID in another document
+ helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-file2", true, "")
+ // test with invalid ID in this document
+ helperForExtractElementID(t, "a:SPDXRef-file1", true, "")
+ helperForExtractElementID(t, "file1", true, "")
+ helperForExtractElementID(t, "SPDXRef-", true, "")
+ helperForExtractElementID(t, "SPDXRef-file1:", true, "")
+ // test with invalid ID in another document
+ helperForExtractElementID(t, "DocumentRef-doc2", true, "")
+ helperForExtractElementID(t, "DocumentRef-doc2:", true, "")
+ helperForExtractElementID(t, "DocumentRef-doc2:SPDXRef-", true, "")
+ helperForExtractElementID(t, "DocumentRef-doc2:a", true, "")
+ helperForExtractElementID(t, "DocumentRef-:", true, "")
+ helperForExtractElementID(t, "DocumentRef-:SPDXRef-file1", true, "")
+}
+
+func helperForExtractElementID(t *testing.T, tst string, wantErr bool, wantElt string) {
+ eID, err := extractElementID(tst)
+ if err != nil && wantErr == false {
+ t.Errorf("testing %v: expected nil error, got %v", tst, err)
+ }
+ if err == nil && wantErr == true {
+ t.Errorf("testing %v: expected non-nil error, got nil", tst)
+ }
+ if eID != common.ElementID(wantElt) {
+ if wantElt == "" {
+ t.Errorf("testing %v: want emptyString for ElementRefID, got %v", tst, eID)
+ } else {
+ t.Errorf("testing %v: want %v for ElementRefID, got %v", tst, wantElt, eID)
+ }
+ }
+}