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 }