github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/golang/license_finder_test.go (about)

     1  package golang
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/spf13/afero"
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  )
    10  
    11  func TestFindAllLicenseCandidatesUpwards(t *testing.T) {
    12  	tests := []struct {
    13  		name          string
    14  		setupFS       func(afero.Fs)
    15  		startDir      string
    16  		stopAt        string
    17  		expectedFiles []string
    18  		expectedError bool
    19  		description   string
    20  	}{
    21  		{
    22  			name:     "normal traversal up to root",
    23  			startDir: "/project/subdir/deeper",
    24  			stopAt:   "/project",
    25  			setupFS: func(fs afero.Fs) {
    26  				fs.MkdirAll("/project/subdir/deeper", 0755)
    27  				afero.WriteFile(fs, "/project/LICENSE", []byte("MIT"), 0644)
    28  				afero.WriteFile(fs, "/project/foobar", []byte("MIT"), 0644)
    29  				afero.WriteFile(fs, "/project/subdir/LICENSE.txt", []byte("Apache"), 0644)
    30  				afero.WriteFile(fs, "/project/subdir/deeper/COPYING", []byte("GPL"), 0644)
    31  			},
    32  			expectedFiles: []string{
    33  				"/project/subdir/deeper/COPYING",
    34  				"/project/subdir/LICENSE.txt",
    35  				"/project/LICENSE",
    36  			},
    37  			description: "Should find all license files traversing upward",
    38  		},
    39  		{
    40  			name:     "stops at boundary directory",
    41  			startDir: "/project/subdir/deeper",
    42  			stopAt:   "/project/subdir",
    43  			setupFS: func(fs afero.Fs) {
    44  				fs.MkdirAll("/project/subdir/deeper", 0755)
    45  				afero.WriteFile(fs, "/project/LICENSE", []byte("MIT"), 0644)
    46  				afero.WriteFile(fs, "/project/subdir/LICENSE.txt", []byte("Apache"), 0644)
    47  				afero.WriteFile(fs, "/project/subdir/deeper/COPYING", []byte("GPL"), 0644)
    48  			},
    49  			expectedFiles: []string{
    50  				"/project/subdir/deeper/COPYING",
    51  				"/project/subdir/LICENSE.txt",
    52  			},
    53  			description: "Should stop at stopAt boundary and not find LICENSE in /project",
    54  		},
    55  		{
    56  			name:     "handles non-existent directory",
    57  			startDir: "/nonexistent",
    58  			stopAt:   "/",
    59  			setupFS: func(fs afero.Fs) {
    60  				// Don't create the directory
    61  			},
    62  			expectedError: true,
    63  			description:   "Should return error for non-existent directory",
    64  		},
    65  		{
    66  			name:     "handles empty directory tree",
    67  			startDir: "/empty/dir/tree",
    68  			stopAt:   "/empty",
    69  			setupFS: func(fs afero.Fs) {
    70  				fs.MkdirAll("/empty/dir/tree", 0755)
    71  				// No license files
    72  			},
    73  			expectedFiles: nil,
    74  			description:   "Should return nil when no license files found",
    75  		},
    76  		{
    77  			name:     "handles directory at filesystem root",
    78  			startDir: "/",
    79  			stopAt:   "/",
    80  			setupFS: func(fs afero.Fs) {
    81  				afero.WriteFile(fs, "/LICENSE", []byte("MIT"), 0644)
    82  			},
    83  			expectedFiles: []string{"/LICENSE"},
    84  			description:   "Should handle traversal starting at root",
    85  		},
    86  		{
    87  			name:     "ignores directories with license-like names",
    88  			startDir: "/project/subdir",
    89  			stopAt:   "/project",
    90  			setupFS: func(fs afero.Fs) {
    91  				fs.MkdirAll("/project/subdir", 0755)
    92  				fs.MkdirAll("/project/LICENSE_DIR", 0755) // Directory, should be ignored
    93  				afero.WriteFile(fs, "/project/LICENSE", []byte("MIT"), 0644)
    94  			},
    95  			expectedFiles: []string{"/project/LICENSE"},
    96  			description:   "Should ignore directories even if they match license pattern",
    97  		},
    98  		{
    99  			name:     "startDir equals stopAt",
   100  			startDir: "/project",
   101  			stopAt:   "/project",
   102  			setupFS: func(fs afero.Fs) {
   103  				fs.MkdirAll("/project", 0755)
   104  				afero.WriteFile(fs, "/project/LICENSE", []byte("MIT"), 0644)
   105  			},
   106  			expectedFiles: []string{"/project/LICENSE"},
   107  			description:   "Should handle case where start equals stop directory",
   108  		},
   109  		{
   110  			name:     "startDir is parent of stopAt (returns empty)",
   111  			startDir: "/",
   112  			stopAt:   "/project",
   113  			setupFS: func(fs afero.Fs) {
   114  				fs.MkdirAll("/project", 0755)
   115  				afero.WriteFile(fs, "/LICENSE", []byte("MIT"), 0644)
   116  			},
   117  			expectedFiles: []string{},
   118  			description:   "Should return empty when startDir is above stopAt",
   119  		},
   120  		{
   121  			name:     "very deep nesting",
   122  			startDir: "/a/b/c/d/e/f/g/h/i/j",
   123  			stopAt:   "/a",
   124  			setupFS: func(fs afero.Fs) {
   125  				fs.MkdirAll("/a/b/c/d/e/f/g/h/i/j", 0755)
   126  				afero.WriteFile(fs, "/a/LICENSE", []byte("MIT"), 0644)
   127  				afero.WriteFile(fs, "/a/b/c/d/e/NOTICE", []byte("Notice"), 0644)
   128  			},
   129  			expectedFiles: []string{
   130  				"/a/b/c/d/e/NOTICE",
   131  				"/a/LICENSE",
   132  			},
   133  			description: "Should handle deep directory nesting without stack overflow",
   134  		},
   135  		{
   136  			name:     "relative dir path rejected",
   137  			startDir: "project/subdir",
   138  			stopAt:   "/project",
   139  			setupFS: func(fs afero.Fs) {
   140  				fs.MkdirAll("/project/subdir", 0755)
   141  			},
   142  			expectedError: true,
   143  			description:   "Should reject relative dir path",
   144  		},
   145  		{
   146  			name:     "relative stopAt path rejected",
   147  			startDir: "/project/subdir",
   148  			stopAt:   "project",
   149  			setupFS: func(fs afero.Fs) {
   150  				fs.MkdirAll("/project/subdir", 0755)
   151  			},
   152  			expectedError: true,
   153  			description:   "Should reject relative stopAt path",
   154  		},
   155  		{
   156  			name:     "stopAt is descendant of startDir",
   157  			startDir: "/project",
   158  			stopAt:   "/project/subdir/deeper",
   159  			setupFS: func(fs afero.Fs) {
   160  				fs.MkdirAll("/project/subdir/deeper", 0755)
   161  				afero.WriteFile(fs, "/project/LICENSE", []byte("MIT"), 0644)
   162  			},
   163  			expectedFiles: []string{},
   164  			description:   "Should return empty when stopAt is below startDir",
   165  		},
   166  		{
   167  			name:     "disjoint paths",
   168  			startDir: "/foo/bar",
   169  			stopAt:   "/baz/qux",
   170  			setupFS: func(fs afero.Fs) {
   171  				fs.MkdirAll("/foo/bar", 0755)
   172  				fs.MkdirAll("/baz/qux", 0755)
   173  				afero.WriteFile(fs, "/foo/bar/LICENSE", []byte("MIT"), 0644)
   174  				afero.WriteFile(fs, "/LICENSE", []byte("Root"), 0644)
   175  			},
   176  			expectedFiles: []string{},
   177  			description:   "Should return empty for completely disjoint paths",
   178  		},
   179  		{
   180  			name:     "empty stopAt rejected",
   181  			startDir: "/project/deep/path",
   182  			stopAt:   "",
   183  			setupFS: func(fs afero.Fs) {
   184  				fs.MkdirAll("/project/deep/path", 0755)
   185  			},
   186  			expectedError: true,
   187  			description:   "Should reject empty stopAt string",
   188  		},
   189  		{
   190  			name:     "empty startDir rejected",
   191  			startDir: "",
   192  			stopAt:   "/project",
   193  			setupFS: func(fs afero.Fs) {
   194  				fs.MkdirAll("/project", 0755)
   195  			},
   196  			expectedError: true,
   197  			description:   "Should reject empty startDir string",
   198  		},
   199  	}
   200  
   201  	for _, tt := range tests {
   202  		t.Run(tt.name, func(t *testing.T) {
   203  			// Create in-memory filesystem
   204  			fs := afero.NewMemMapFs()
   205  			tt.setupFS(fs)
   206  
   207  			// Run the function
   208  			result, err := findAllLicenseCandidatesUpwards(tt.startDir, tt.stopAt, fs)
   209  
   210  			// Check error expectation
   211  			if tt.expectedError {
   212  				assert.Error(t, err, tt.description)
   213  				return
   214  			}
   215  
   216  			require.NoError(t, err, tt.description)
   217  			assert.Equal(t, tt.expectedFiles, result, tt.description)
   218  		})
   219  	}
   220  }