github.com/anchore/syft@v1.38.2/syft/pkg/license_test.go (about)

     1  package pkg
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/assert"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	"github.com/anchore/syft/syft/artifact"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/license"
    14  )
    15  
    16  func Test_Hash(t *testing.T) {
    17  	ctx := context.TODO()
    18  	loc1 := file.NewLocation("place!")
    19  	loc1.FileSystemID = "fs1"
    20  	loc2 := file.NewLocation("place!")
    21  	loc2.FileSystemID = "fs2" // important! there is a different file system ID
    22  
    23  	lic1 := NewLicenseFromFieldsWithContext(ctx, "MIT", "foo", &loc1)
    24  	lic2 := NewLicenseFromFieldsWithContext(ctx, "MIT", "bar", &loc2)
    25  
    26  	hash1, err := artifact.IDByHash(lic1)
    27  	require.NoError(t, err)
    28  
    29  	hash2, err := artifact.IDByHash(lic2)
    30  	require.NoError(t, err)
    31  
    32  	assert.Equal(t, hash1, hash2)
    33  }
    34  
    35  func Test_Sort(t *testing.T) {
    36  	ctx := context.TODO()
    37  	tests := []struct {
    38  		name     string
    39  		licenses Licenses
    40  		expected Licenses
    41  	}{
    42  		{
    43  			name:     "empty",
    44  			licenses: []License{},
    45  			expected: []License{},
    46  		},
    47  		{
    48  			name: "single",
    49  			licenses: []License{
    50  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("place!")),
    51  			},
    52  			expected: []License{
    53  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("place!")),
    54  			},
    55  		},
    56  		{
    57  			name: "multiple",
    58  			licenses: []License{
    59  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("place!")),
    60  				NewLicenseFromURLsWithContext(ctx, "MIT", "https://github.com/anchore/syft/blob/main/LICENSE"),
    61  				NewLicenseFromLocationsWithContext(ctx, "Apache", file.NewLocation("area!")),
    62  				NewLicenseFromLocationsWithContext(ctx, "gpl2+", file.NewLocation("area!")),
    63  			},
    64  			expected: Licenses{
    65  				NewLicenseFromLocationsWithContext(ctx, "Apache", file.NewLocation("area!")),
    66  				NewLicenseFromURLsWithContext(ctx, "MIT", "https://github.com/anchore/syft/blob/main/LICENSE"),
    67  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("place!")),
    68  				NewLicenseFromLocationsWithContext(ctx, "gpl2+", file.NewLocation("area!")),
    69  			},
    70  		},
    71  		{
    72  			name: "multiple with location variants",
    73  			licenses: []License{
    74  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("place!")),
    75  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("park!")),
    76  				NewLicenseWithContext(ctx, "MIT"),
    77  				NewLicenseWithContext(ctx, "AAL"),
    78  				NewLicenseWithContext(ctx, "Adobe-2006"),
    79  				NewLicenseFromLocationsWithContext(ctx, "Apache", file.NewLocation("area!")),
    80  			},
    81  			expected: Licenses{
    82  				NewLicenseWithContext(ctx, "AAL"),
    83  				NewLicenseWithContext(ctx, "Adobe-2006"),
    84  				NewLicenseFromLocationsWithContext(ctx, "Apache", file.NewLocation("area!")),
    85  				NewLicenseWithContext(ctx, "MIT"),
    86  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("park!")),
    87  				NewLicenseFromLocationsWithContext(ctx, "MIT", file.NewLocation("place!")),
    88  			},
    89  		},
    90  		{
    91  			name: "multiple licenses with only contents are still sorted by their computed lic.value references",
    92  			licenses: []License{
    93  				NewLicenseWithContext(ctx, readFileAsString("../../internal/licenses/test-fixtures/nvidia-software-and-cuda-supplement")),
    94  				NewLicenseWithContext(ctx, readFileAsString("../../internal/licenses/test-fixtures/Knuth-CTAN")),
    95  				NewLicenseWithContext(ctx, readFileAsString("../../internal/licenses/test-fixtures/apache-license-2.0")),
    96  			},
    97  			expected: Licenses{
    98  				NewLicenseWithContext(ctx, readFileAsString("../../internal/licenses/test-fixtures/apache-license-2.0")),
    99  				NewLicenseWithContext(ctx, readFileAsString("../../internal/licenses/test-fixtures/nvidia-software-and-cuda-supplement")),
   100  				NewLicenseWithContext(ctx, readFileAsString("../../internal/licenses/test-fixtures/Knuth-CTAN")),
   101  			},
   102  		},
   103  	}
   104  
   105  	for _, test := range tests {
   106  		t.Run(test.name, func(t *testing.T) {
   107  			sort.Sort(test.licenses)
   108  			assert.Equal(t, test.expected, test.licenses)
   109  		})
   110  
   111  	}
   112  }
   113  
   114  func TestLicense_Merge(t *testing.T) {
   115  	locA := file.NewLocation("a")
   116  	locB := file.NewLocation("b")
   117  
   118  	tests := []struct {
   119  		name    string
   120  		subject License
   121  		other   License
   122  		want    License
   123  		wantErr require.ErrorAssertionFunc
   124  	}{
   125  		{
   126  			name: "valid merge",
   127  			subject: License{
   128  				Value:          "MIT",
   129  				SPDXExpression: "MIT",
   130  				Type:           license.Declared,
   131  				URLs: []string{
   132  					"b", "a",
   133  				},
   134  				Locations: file.NewLocationSet(locA),
   135  			},
   136  			other: License{
   137  				Value:          "MIT",
   138  				SPDXExpression: "MIT",
   139  				Type:           license.Declared,
   140  				URLs: []string{
   141  					"c", "d",
   142  				},
   143  				Locations: file.NewLocationSet(locB),
   144  			},
   145  			want: License{
   146  				Value:          "MIT",
   147  				SPDXExpression: "MIT",
   148  				Type:           license.Declared,
   149  				URLs: []string{
   150  					"a", "b", "c", "d",
   151  				},
   152  				Locations: file.NewLocationSet(locA, locB),
   153  			},
   154  		},
   155  		{
   156  			name: "mismatched value",
   157  			subject: License{
   158  				Value:          "DIFFERENT!!",
   159  				SPDXExpression: "MIT",
   160  				Type:           license.Declared,
   161  				URLs: []string{
   162  					"b", "a",
   163  				},
   164  				Locations: file.NewLocationSet(locA),
   165  			},
   166  			other: License{
   167  				Value:          "MIT",
   168  				SPDXExpression: "MIT",
   169  				Type:           license.Declared,
   170  				URLs: []string{
   171  					"c", "d",
   172  				},
   173  				Locations: file.NewLocationSet(locB),
   174  			},
   175  			wantErr: require.Error,
   176  		},
   177  		{
   178  			name: "mismatched spdx expression",
   179  			subject: License{
   180  				Value:          "MIT",
   181  				SPDXExpression: "DIFFERENT!!",
   182  				Type:           license.Declared,
   183  				URLs: []string{
   184  					"b", "a",
   185  				},
   186  				Locations: file.NewLocationSet(locA),
   187  			},
   188  			other: License{
   189  				Value:          "MIT",
   190  				SPDXExpression: "MIT",
   191  				Type:           license.Declared,
   192  				URLs: []string{
   193  					"c", "d",
   194  				},
   195  				Locations: file.NewLocationSet(locB),
   196  			},
   197  			wantErr: require.Error,
   198  		},
   199  		{
   200  			name: "mismatched type",
   201  			subject: License{
   202  				Value:          "MIT",
   203  				SPDXExpression: "MIT",
   204  				Type:           license.Concluded,
   205  				URLs: []string{
   206  					"b", "a",
   207  				},
   208  				Locations: file.NewLocationSet(locA),
   209  			},
   210  			other: License{
   211  				Value:          "MIT",
   212  				SPDXExpression: "MIT",
   213  				Type:           license.Declared,
   214  				URLs: []string{
   215  					"c", "d",
   216  				},
   217  				Locations: file.NewLocationSet(locB),
   218  			},
   219  			wantErr: require.Error,
   220  		},
   221  	}
   222  	for _, tt := range tests {
   223  		t.Run(tt.name, func(t *testing.T) {
   224  			if tt.wantErr == nil {
   225  				tt.wantErr = require.NoError
   226  			}
   227  
   228  			subjectLocationLen := len(tt.subject.Locations.ToSlice())
   229  			subjectURLLen := len(tt.subject.URLs)
   230  
   231  			got, err := tt.subject.Merge(tt.other)
   232  			tt.wantErr(t, err)
   233  			if err != nil {
   234  				return
   235  			}
   236  			require.NotNilf(t, got, "expected a non-nil license")
   237  			assert.Equal(t, tt.want, *got)
   238  			// prove we don't modify the subject
   239  			assert.Equal(t, subjectLocationLen, len(tt.subject.Locations.ToSlice()))
   240  			assert.Equal(t, subjectURLLen, len(tt.subject.URLs))
   241  		})
   242  	}
   243  }
   244  
   245  func TestFullText(t *testing.T) {
   246  	ctx := context.TODO()
   247  	fullText := `I am a license with full text
   248  	my authors put new line characters in metadata for labeling a license`
   249  	tests := []struct {
   250  		name  string
   251  		value string
   252  		want  License
   253  	}{
   254  		{
   255  			name:  "Full Text field is populated with the correct full text and contents are given a sha256 as value",
   256  			value: fullText,
   257  			want: License{
   258  				Value:    "sha256:108067fa71229a2b98b9696af0ce21cd11d9639634c8bc94bda70ebedf291e5a",
   259  				Type:     license.Declared,
   260  				Contents: fullText,
   261  			},
   262  		},
   263  	}
   264  
   265  	for _, tt := range tests {
   266  		t.Run(tt.name, func(t *testing.T) {
   267  			got := NewLicenseWithContext(ctx, tt.value)
   268  			assert.Equal(t, tt.want, got)
   269  		})
   270  	}
   271  }
   272  
   273  func TestLicenseConstructors(t *testing.T) {
   274  	ctx := context.TODO()
   275  	type input struct {
   276  		value string
   277  		urls  []string
   278  	}
   279  	tests := []struct {
   280  		name     string
   281  		input    input
   282  		expected License
   283  	}{
   284  		{
   285  			name: "License URLs are stripped of newlines and tabs",
   286  			input: input{
   287  				value: "New BSD License",
   288  				urls: []string{
   289  					`
   290  						http://user-agent-utils.googlecode.com/svn/trunk/UserAgentUtils/LICENSE.txt
   291  					`},
   292  			},
   293  			expected: License{
   294  				Value: "New BSD License",
   295  				Type:  license.Declared,
   296  				URLs:  []string{"http://user-agent-utils.googlecode.com/svn/trunk/UserAgentUtils/LICENSE.txt"},
   297  			},
   298  		},
   299  		{
   300  			name: "License URLs without value",
   301  			input: input{
   302  				value: "",
   303  				urls:  []string{"http://user-agent-utils.googlecode.com/svn/trunk/UserAgentUtils/LICENSE.txt"},
   304  			},
   305  			expected: License{
   306  				Type: license.Declared,
   307  				URLs: []string{"http://user-agent-utils.googlecode.com/svn/trunk/UserAgentUtils/LICENSE.txt"},
   308  			},
   309  		},
   310  	}
   311  
   312  	for _, test := range tests {
   313  		t.Run(test.name, func(t *testing.T) {
   314  			got := NewLicenseFromURLsWithContext(ctx, test.input.value, test.input.urls...)
   315  			assert.Equal(t, test.expected, got)
   316  		})
   317  	}
   318  }