github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/internal/cpegenerate/generate_test.go (about) 1 package cpegenerate 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "testing" 8 9 "github.com/scylladb/go-set" 10 "github.com/scylladb/go-set/strset" 11 "github.com/stretchr/testify/assert" 12 13 "github.com/anchore/syft/syft/cpe" 14 "github.com/anchore/syft/syft/pkg" 15 ) 16 17 func keyValues(m map[string]string) []pkg.KeyValue { 18 var kvs []pkg.KeyValue 19 for k, v := range m { 20 kvs = append(kvs, pkg.KeyValue{ 21 Key: k, 22 Value: v, 23 }) 24 } 25 return kvs 26 } 27 28 func TestGeneratePackageCPEs(t *testing.T) { 29 tests := []struct { 30 name string 31 p pkg.Package 32 expected []string 33 }{ 34 { 35 name: "hyphen replacement", 36 p: pkg.Package{ 37 Name: "name-part", 38 Version: "3.2", 39 FoundBy: "some-analyzer", 40 Language: pkg.Python, 41 Type: pkg.DebPkg, 42 }, 43 expected: []string{ 44 "cpe:2.3:a:name-part:name-part:3.2:*:*:*:*:*:*:*", 45 "cpe:2.3:a:name-part:name_part:3.2:*:*:*:*:*:*:*", 46 "cpe:2.3:a:name-part:python-name-part:3.2:*:*:*:*:*:*:*", 47 "cpe:2.3:a:name-part:python_name_part:3.2:*:*:*:*:*:*:*", 48 "cpe:2.3:a:name:name-part:3.2:*:*:*:*:*:*:*", 49 "cpe:2.3:a:name:name_part:3.2:*:*:*:*:*:*:*", 50 "cpe:2.3:a:name:python-name-part:3.2:*:*:*:*:*:*:*", 51 "cpe:2.3:a:name:python_name_part:3.2:*:*:*:*:*:*:*", 52 "cpe:2.3:a:name_part:name-part:3.2:*:*:*:*:*:*:*", 53 "cpe:2.3:a:name_part:name_part:3.2:*:*:*:*:*:*:*", 54 "cpe:2.3:a:name_part:python-name-part:3.2:*:*:*:*:*:*:*", 55 "cpe:2.3:a:name_part:python_name_part:3.2:*:*:*:*:*:*:*", 56 "cpe:2.3:a:python-name-part:name-part:3.2:*:*:*:*:*:*:*", 57 "cpe:2.3:a:python-name-part:name_part:3.2:*:*:*:*:*:*:*", 58 "cpe:2.3:a:python-name-part:python-name-part:3.2:*:*:*:*:*:*:*", 59 "cpe:2.3:a:python-name-part:python_name_part:3.2:*:*:*:*:*:*:*", 60 "cpe:2.3:a:python-name:name-part:3.2:*:*:*:*:*:*:*", 61 "cpe:2.3:a:python-name:name_part:3.2:*:*:*:*:*:*:*", 62 "cpe:2.3:a:python-name:python-name-part:3.2:*:*:*:*:*:*:*", 63 "cpe:2.3:a:python-name:python_name_part:3.2:*:*:*:*:*:*:*", 64 "cpe:2.3:a:python:name-part:3.2:*:*:*:*:*:*:*", 65 "cpe:2.3:a:python:name_part:3.2:*:*:*:*:*:*:*", 66 "cpe:2.3:a:python:python-name-part:3.2:*:*:*:*:*:*:*", 67 "cpe:2.3:a:python:python_name_part:3.2:*:*:*:*:*:*:*", 68 "cpe:2.3:a:python_name:name-part:3.2:*:*:*:*:*:*:*", 69 "cpe:2.3:a:python_name:name_part:3.2:*:*:*:*:*:*:*", 70 "cpe:2.3:a:python_name:python-name-part:3.2:*:*:*:*:*:*:*", 71 "cpe:2.3:a:python_name:python_name_part:3.2:*:*:*:*:*:*:*", 72 "cpe:2.3:a:python_name_part:name-part:3.2:*:*:*:*:*:*:*", 73 "cpe:2.3:a:python_name_part:name_part:3.2:*:*:*:*:*:*:*", 74 "cpe:2.3:a:python_name_part:python-name-part:3.2:*:*:*:*:*:*:*", 75 "cpe:2.3:a:python_name_part:python_name_part:3.2:*:*:*:*:*:*:*", 76 }, 77 }, 78 { 79 name: "python language", 80 p: pkg.Package{ 81 Name: "name", 82 Version: "3.2", 83 FoundBy: "some-analyzer", 84 Language: pkg.Python, 85 Type: pkg.DebPkg, 86 Metadata: pkg.PythonPackage{ 87 Author: "alex goodman", 88 AuthorEmail: "william.goodman@anchore.com", 89 }, 90 }, 91 expected: []string{ 92 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 93 "cpe:2.3:a:name:python-name:3.2:*:*:*:*:*:*:*", 94 "cpe:2.3:a:name:python_name:3.2:*:*:*:*:*:*:*", 95 "cpe:2.3:a:python-name:name:3.2:*:*:*:*:*:*:*", 96 "cpe:2.3:a:python-name:python-name:3.2:*:*:*:*:*:*:*", 97 "cpe:2.3:a:python-name:python_name:3.2:*:*:*:*:*:*:*", 98 "cpe:2.3:a:python:name:3.2:*:*:*:*:*:*:*", 99 "cpe:2.3:a:python:python-name:3.2:*:*:*:*:*:*:*", 100 "cpe:2.3:a:python:python_name:3.2:*:*:*:*:*:*:*", 101 "cpe:2.3:a:python_name:name:3.2:*:*:*:*:*:*:*", 102 "cpe:2.3:a:python_name:python-name:3.2:*:*:*:*:*:*:*", 103 "cpe:2.3:a:python_name:python_name:3.2:*:*:*:*:*:*:*", 104 "cpe:2.3:a:alex_goodman:name:3.2:*:*:*:*:*:*:*", 105 "cpe:2.3:a:alex_goodman:python-name:3.2:*:*:*:*:*:*:*", 106 "cpe:2.3:a:alex_goodman:python_name:3.2:*:*:*:*:*:*:*", 107 "cpe:2.3:a:william-goodman:name:3.2:*:*:*:*:*:*:*", 108 "cpe:2.3:a:william-goodman:python-name:3.2:*:*:*:*:*:*:*", 109 "cpe:2.3:a:william-goodman:python_name:3.2:*:*:*:*:*:*:*", 110 "cpe:2.3:a:william_goodman:name:3.2:*:*:*:*:*:*:*", 111 "cpe:2.3:a:william_goodman:python-name:3.2:*:*:*:*:*:*:*", 112 "cpe:2.3:a:william_goodman:python_name:3.2:*:*:*:*:*:*:*", 113 "cpe:2.3:a:alex_goodman_project:python_name:3.2:*:*:*:*:*:*:*", 114 "cpe:2.3:a:alex_goodman_project:name:3.2:*:*:*:*:*:*:*", 115 "cpe:2.3:a:alex_goodman_project:python-name:3.2:*:*:*:*:*:*:*", 116 "cpe:2.3:a:alex_goodmanproject:name:3.2:*:*:*:*:*:*:*", 117 "cpe:2.3:a:alex_goodmanproject:python-name:3.2:*:*:*:*:*:*:*", 118 "cpe:2.3:a:alex_goodmanproject:python_name:3.2:*:*:*:*:*:*:*", 119 "cpe:2.3:a:william_goodman_project:name:3.2:*:*:*:*:*:*:*", 120 "cpe:2.3:a:william_goodman_project:python-name:3.2:*:*:*:*:*:*:*", 121 "cpe:2.3:a:william_goodman_project:python_name:3.2:*:*:*:*:*:*:*", 122 "cpe:2.3:a:william_goodmanproject:name:3.2:*:*:*:*:*:*:*", 123 "cpe:2.3:a:william_goodmanproject:python-name:3.2:*:*:*:*:*:*:*", 124 "cpe:2.3:a:william_goodmanproject:python_name:3.2:*:*:*:*:*:*:*", 125 }, 126 }, 127 { 128 name: "javascript language", 129 p: pkg.Package{ 130 Name: "name", 131 Version: "3.2", 132 FoundBy: "some-analyzer", 133 Language: pkg.JavaScript, 134 Metadata: pkg.NpmPackage{ 135 Author: "jon", 136 URL: "https://github.com/bob/npm-name", 137 }, 138 }, 139 expected: []string{ 140 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 141 "cpe:2.3:a:bob:name:3.2:*:*:*:*:*:*:*", 142 }, 143 }, 144 { 145 name: "ruby language", 146 p: pkg.Package{ 147 Name: "name", 148 Version: "3.2", 149 FoundBy: "some-analyzer", 150 Language: pkg.Ruby, 151 Type: pkg.DebPkg, 152 Metadata: pkg.RubyGemspec{ 153 Authors: []string{ 154 "someones name", 155 "someones.elses.name@gmail.com", 156 }, 157 Homepage: "https://github.com/tom/ruby-name", 158 }, 159 }, 160 expected: []string{ 161 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 162 "cpe:2.3:a:ruby-lang:name:3.2:*:*:*:*:*:*:*", 163 "cpe:2.3:a:ruby:name:3.2:*:*:*:*:*:*:*", 164 "cpe:2.3:a:ruby_lang:name:3.2:*:*:*:*:*:*:*", 165 "cpe:2.3:a:someones-elses-name:name:3.2:*:*:*:*:*:*:*", 166 "cpe:2.3:a:someones-name:name:3.2:*:*:*:*:*:*:*", 167 "cpe:2.3:a:someones_elses_name:name:3.2:*:*:*:*:*:*:*", 168 "cpe:2.3:a:someones_name:name:3.2:*:*:*:*:*:*:*", 169 "cpe:2.3:a:tom:name:3.2:*:*:*:*:*:*:*", 170 }, 171 }, 172 { 173 name: "java language", 174 p: pkg.Package{ 175 Name: "name", 176 Version: "3.2", 177 FoundBy: "some-analyzer", 178 Language: pkg.Java, 179 Type: pkg.JavaPkg, 180 }, 181 expected: []string{ 182 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 183 }, 184 }, 185 { 186 name: "java language with groupID", 187 p: pkg.Package{ 188 Name: "name", 189 Version: "3.2", 190 FoundBy: "some-analyzer", 191 Language: pkg.Java, 192 Type: pkg.JavaPkg, 193 Metadata: pkg.JavaArchive{ 194 PomProperties: &pkg.JavaPomProperties{ 195 GroupID: "org.sonatype.nexus", 196 }, 197 }, 198 }, 199 expected: []string{ 200 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 201 "cpe:2.3:a:name:nexus:3.2:*:*:*:*:*:*:*", 202 "cpe:2.3:a:nexus:name:3.2:*:*:*:*:*:*:*", 203 "cpe:2.3:a:nexus:nexus:3.2:*:*:*:*:*:*:*", 204 "cpe:2.3:a:sonatype:name:3.2:*:*:*:*:*:*:*", 205 "cpe:2.3:a:sonatype:nexus:3.2:*:*:*:*:*:*:*", 206 }, 207 }, 208 { 209 name: "java with URL in metadata", // regression: https://github.com/anchore/grype/issues/417 210 p: pkg.Package{ 211 Name: "wstx-asl", 212 Version: "3.2.7", 213 Type: pkg.JavaPkg, 214 Metadata: pkg.JavaArchive{ 215 Manifest: &pkg.JavaManifest{ 216 Main: keyValues(map[string]string{ 217 "Ant-Version": "Apache Ant 1.6.5", 218 "Built-By": "tatu", 219 "Created-By": "1.4.2_03-b02 (Sun Microsystems Inc.)", 220 "Implementation-Title": "WoodSToX XML-processor", 221 "Implementation-Vendor": "woodstox.codehaus.org", 222 "Implementation-Version": "3.2.7", 223 "Manifest-Version": "1.0", 224 "Specification-Title": "StAX 1.0 API", 225 "Specification-Vendor": "http://jcp.org/en/jsr/detail?id=173", 226 "Specification-Version": "1.0", 227 }), 228 }, 229 }, 230 }, 231 expected: []string{ 232 "cpe:2.3:a:woodstox_codehaus_org:wstx-asl:3.2.7:*:*:*:*:*:*:*", 233 "cpe:2.3:a:woodstox_codehaus_org:wstx_asl:3.2.7:*:*:*:*:*:*:*", 234 "cpe:2.3:a:woodstox-codehaus-org:wstx_asl:3.2.7:*:*:*:*:*:*:*", 235 "cpe:2.3:a:woodstox-codehaus-org:wstx-asl:3.2.7:*:*:*:*:*:*:*", 236 "cpe:2.3:a:wstx_asl:wstx-asl:3.2.7:*:*:*:*:*:*:*", 237 "cpe:2.3:a:wstx-asl:wstx-asl:3.2.7:*:*:*:*:*:*:*", 238 "cpe:2.3:a:wstx-asl:wstx_asl:3.2.7:*:*:*:*:*:*:*", 239 "cpe:2.3:a:wstx_asl:wstx_asl:3.2.7:*:*:*:*:*:*:*", 240 "cpe:2.3:a:wstx:wstx_asl:3.2.7:*:*:*:*:*:*:*", 241 "cpe:2.3:a:wstx:wstx-asl:3.2.7:*:*:*:*:*:*:*", 242 }, 243 }, 244 { 245 name: "jenkins package identified via pkg type", 246 p: pkg.Package{ 247 Name: "name", 248 Version: "3.2", 249 FoundBy: "some-analyzer", 250 Language: pkg.Java, 251 Type: pkg.JenkinsPluginPkg, 252 }, 253 expected: []string{ 254 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 255 }, 256 }, 257 { 258 name: "java language - multi tier manifest fields", 259 p: pkg.Package{ 260 Name: "cxf-rt-bindings-xml", 261 Version: "3.3.10", 262 FoundBy: "java-cataloger", 263 Language: pkg.Java, 264 Type: pkg.JavaPkg, 265 Metadata: pkg.JavaArchive{ 266 VirtualPath: "/opt/jboss/keycloak/modules/system/layers/base/org/apache/cxf/impl/main/cxf-rt-bindings-xml-3.3.10.jar", 267 Manifest: &pkg.JavaManifest{ 268 Main: keyValues(map[string]string{ 269 "Automatic-Module-Name": "org.apache.cxf.binding.xml", 270 "Bnd-LastModified": "1615836524860", 271 "Build-Jdk": "1.8.0_261", 272 "Built-By": "dkulp", 273 "Bundle-ActivationPolicy": "lazy", 274 "Bundle-Description": "Apache CXF Runtime XML Binding", 275 "Bundle-DocURL": "http://cxf.apache.org", 276 "Bundle-License": "https://www.apache.org/licenses/LICENSE-2.0.txt", 277 "Bundle-ManifestVersion": "2", 278 "Bundle-Name": "Apache CXF Runtime XML Binding", 279 "Bundle-SymbolicName": "org.apache.cxf.cxf-rt-bindings-xml", 280 "Bundle-Vendor": "The Apache Software Foundation", 281 "Bundle-Version": "3.3.10", 282 "Created-By": "Apache Maven Bundle Plugin", 283 "Export-Package": "org.apache.cxf.binding.xml;version=\"3.3.10\",org.apache.cxf.binding.xml.wsdl11;version=\"3.3.10\",org.apache.cxf.binding.xml.interceptor;version=\"3.3.10\",org.apache.cxf.bindings.xformat;version=\"3.3.10\"", 284 "Implementation-Vendor": "The Apache Software Foundation", 285 "Implementation-Vendor-Id": "org.apache", 286 "Implementation-Version": "3.3.10", 287 "Import-Package": "javax.xml.bind;version=\"[0,3)\",javax.xml.bind.annotation;version=\"[0,3)\",javax.wsdl;resolution:=optional,javax.wsdl.extensions;resolution:=optional,javax.wsdl.extensions.http;resolution:=optional,javax.xml.namespace,javax.xml.stream,org.apache.cxf;version=\"[3.3,4)\",org.apache.cxf.binding;version=\"[3.3,4)\",org.apache.cxf.binding.xml,org.apache.cxf.binding.xml.interceptor,org.apache.cxf.bindings.xformat,org.apache.cxf.common.i18n;version=\"[3.3,4)\",org.apache.cxf.common.injection;version=\"[3.3,4)\",org.apache.cxf.common.logging;version=\"[3.3,4)\",org.apache.cxf.common.util;version=\"[3.3,4)\",org.apache.cxf.endpoint;version=\"[3.3,4)\",org.apache.cxf.helpers;version=\"[3.3,4)\",org.apache.cxf.interceptor;version=\"[3.3,4)\",org.apache.cxf.message;version=\"[3.3,4)\",org.apache.cxf.service.model;version=\"[3.3,4)\",org.apache.cxf.staxutils;version=\"[3.3,4)\",org.apache.cxf.tools.common;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.tools.validator;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.transport;version=\"[3.3,4)\",org.apache.cxf.wsdl;version=\"[3.3,4)\";resolution:=optional,org.apache.cxf.wsdl.http;version=\"[3.3,4)\",org.apache.cxf.wsdl.interceptors;version=\"[3.3,4)\";resolution:=optional,org.w3c.dom", 288 "Manifest-Version": "1.0", 289 "Require-Capability": "osgi.ee;filter:=\"(&(osgi.ee=JavaSE)(version=1.8))\"", 290 "Specification-Vendor": "The Apache Software Foundation", 291 "Specification-Version": "3.3.10", 292 "Tool": "Bnd-4.2.0.201903051501", 293 }), 294 }, 295 PomProperties: &pkg.JavaPomProperties{ 296 Path: "META-INF/maven/org.apache.cxf/cxf-rt-bindings-xml/pom.properties", 297 GroupID: "org.apache.cxf", 298 ArtifactID: "cxf-rt-bindings-xml", 299 Version: "3.3.10", 300 }, 301 }, 302 }, 303 expected: []string{ 304 "cpe:2.3:a:apache:cxf-rt-bindings-xml:3.3.10:*:*:*:*:*:*:*", 305 "cpe:2.3:a:apache:cxf:3.3.10:*:*:*:*:*:*:*", 306 "cpe:2.3:a:apache:cxf_rt_bindings_xml:3.3.10:*:*:*:*:*:*:*", 307 }, 308 }, 309 { 310 name: "rpm vendor selection", 311 p: pkg.Package{ 312 Name: "name", 313 Version: "3.2", 314 FoundBy: "some-analyzer", 315 Type: pkg.RpmPkg, 316 Metadata: pkg.RpmDBEntry{ 317 Vendor: "some-vendor", 318 }, 319 }, 320 expected: []string{ 321 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 322 "cpe:2.3:a:some-vendor:name:3.2:*:*:*:*:*:*:*", 323 "cpe:2.3:a:some_vendor:name:3.2:*:*:*:*:*:*:*", 324 }, 325 }, 326 { 327 name: "rpm with epoch", 328 p: pkg.Package{ 329 Name: "name", 330 Version: "1:3.2", 331 FoundBy: "some-analyzer", 332 Type: pkg.RpmPkg, 333 Metadata: pkg.RpmDBEntry{ 334 Vendor: "some-vendor", 335 }, 336 }, 337 expected: []string{ 338 "cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*", 339 "cpe:2.3:a:some-vendor:name:1\\:3.2:*:*:*:*:*:*:*", 340 "cpe:2.3:a:some_vendor:name:1\\:3.2:*:*:*:*:*:*:*", 341 }, 342 }, 343 { 344 name: "deb with epoch", 345 p: pkg.Package{ 346 Name: "name", 347 Version: "1:3.2", 348 FoundBy: "some-analyzer", 349 Type: pkg.DebPkg, 350 Metadata: pkg.DpkgDBEntry{}, 351 }, 352 expected: []string{ 353 "cpe:2.3:a:name:name:1\\:3.2:*:*:*:*:*:*:*", 354 }, 355 }, 356 { 357 name: "cloudbees jenkins package identified via groupId", 358 p: pkg.Package{ 359 Name: "name", 360 Version: "3.2", 361 FoundBy: "some-analyzer", 362 Language: pkg.Java, 363 Type: pkg.JenkinsPluginPkg, 364 Metadata: pkg.JavaArchive{ 365 PomProperties: &pkg.JavaPomProperties{ 366 GroupID: "com.cloudbees.jenkins.plugins", 367 }, 368 }, 369 }, 370 expected: []string{ 371 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 372 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 373 "cpe:2.3:a:cloudbees:name:3.2:*:*:*:*:*:*:*", 374 }, 375 }, 376 { 377 name: "jenkins.io package identified via groupId prefix", 378 p: pkg.Package{ 379 Name: "name", 380 Version: "3.2", 381 FoundBy: "some-analyzer", 382 Language: pkg.Java, 383 Type: pkg.JenkinsPluginPkg, 384 Metadata: pkg.JavaArchive{ 385 PomProperties: &pkg.JavaPomProperties{ 386 GroupID: "io.jenkins.plugins.name.something", 387 }, 388 }, 389 }, 390 expected: []string{ 391 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 392 "cpe:2.3:a:name:something:3.2:*:*:*:*:*:*:*", 393 "cpe:2.3:a:something:name:3.2:*:*:*:*:*:*:*", 394 "cpe:2.3:a:something:something:3.2:*:*:*:*:*:*:*", 395 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 396 "cpe:2.3:a:jenkins:something:3.2:*:*:*:*:*:*:*", 397 }, 398 }, 399 { 400 name: "jenkins.io package identified via groupId", 401 p: pkg.Package{ 402 Name: "name", 403 Version: "3.2", 404 FoundBy: "some-analyzer", 405 Language: pkg.Java, 406 Type: pkg.JenkinsPluginPkg, 407 Metadata: pkg.JavaArchive{ 408 PomProperties: &pkg.JavaPomProperties{ 409 GroupID: "io.jenkins.plugins", 410 }, 411 }, 412 }, 413 expected: []string{ 414 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 415 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 416 }, 417 }, 418 { 419 name: "jenkins-ci.io package identified via groupId", 420 p: pkg.Package{ 421 Name: "name", 422 Version: "3.2", 423 FoundBy: "some-analyzer", 424 Language: pkg.Java, 425 Type: pkg.JenkinsPluginPkg, 426 Metadata: pkg.JavaArchive{ 427 PomProperties: &pkg.JavaPomProperties{ 428 GroupID: "io.jenkins-ci.plugins", 429 }, 430 }, 431 }, 432 expected: []string{ 433 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 434 "cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*", 435 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 436 "cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*", 437 }, 438 }, 439 { 440 name: "jenkins-ci.org package identified via groupId", 441 p: pkg.Package{ 442 Name: "name", 443 Version: "3.2", 444 FoundBy: "some-analyzer", 445 Language: pkg.Java, 446 Type: pkg.JenkinsPluginPkg, 447 Metadata: pkg.JavaArchive{ 448 PomProperties: &pkg.JavaPomProperties{ 449 GroupID: "org.jenkins-ci.plugins", 450 }, 451 }, 452 }, 453 expected: []string{ 454 "cpe:2.3:a:name:name:3.2:*:*:*:*:*:*:*", 455 "cpe:2.3:a:jenkins-ci:name:3.2:*:*:*:*:*:*:*", 456 "cpe:2.3:a:jenkins:name:3.2:*:*:*:*:*:*:*", 457 "cpe:2.3:a:jenkins_ci:name:3.2:*:*:*:*:*:*:*", 458 }, 459 }, 460 { 461 name: "jira-atlassian filtering", 462 p: pkg.Package{ 463 Name: "jira_client_core", 464 Version: "3.2", 465 FoundBy: "some-analyzer", 466 Language: pkg.Java, 467 Type: pkg.JavaPkg, 468 Metadata: pkg.JavaArchive{ 469 PomProperties: &pkg.JavaPomProperties{ 470 GroupID: "org.atlassian.jira", 471 ArtifactID: "jira_client_core", 472 }, 473 }, 474 }, 475 expected: []string{ 476 "cpe:2.3:a:atlassian:jira-client-core:3.2:*:*:*:*:*:*:*", 477 "cpe:2.3:a:atlassian:jira_client_core:3.2:*:*:*:*:*:*:*", 478 "cpe:2.3:a:jira-client-core:jira-client-core:3.2:*:*:*:*:*:*:*", 479 "cpe:2.3:a:jira-client-core:jira:3.2:*:*:*:*:*:*:*", 480 "cpe:2.3:a:jira-client-core:jira_client_core:3.2:*:*:*:*:*:*:*", 481 "cpe:2.3:a:jira-client:jira-client-core:3.2:*:*:*:*:*:*:*", 482 "cpe:2.3:a:jira-client:jira:3.2:*:*:*:*:*:*:*", 483 "cpe:2.3:a:jira-client:jira_client_core:3.2:*:*:*:*:*:*:*", 484 "cpe:2.3:a:jira:jira-client-core:3.2:*:*:*:*:*:*:*", 485 "cpe:2.3:a:jira:jira_client_core:3.2:*:*:*:*:*:*:*", 486 "cpe:2.3:a:jira_client:jira-client-core:3.2:*:*:*:*:*:*:*", 487 "cpe:2.3:a:jira_client:jira:3.2:*:*:*:*:*:*:*", 488 "cpe:2.3:a:jira_client:jira_client_core:3.2:*:*:*:*:*:*:*", 489 "cpe:2.3:a:jira_client_core:jira-client-core:3.2:*:*:*:*:*:*:*", 490 "cpe:2.3:a:jira_client_core:jira:3.2:*:*:*:*:*:*:*", 491 "cpe:2.3:a:jira_client_core:jira_client_core:3.2:*:*:*:*:*:*:*", 492 }, 493 }, 494 { 495 name: "jenkins filtering", 496 p: pkg.Package{ 497 Name: "cloudbees-installation-manager", 498 Version: "2.89.0.33", 499 FoundBy: "some-analyzer", 500 Language: pkg.Java, 501 Type: pkg.JavaPkg, 502 Metadata: pkg.JavaArchive{ 503 PomProperties: &pkg.JavaPomProperties{ 504 GroupID: "com.cloudbees.jenkins.modules", 505 ArtifactID: "cloudbees-installation-manager", 506 }, 507 }, 508 }, 509 expected: []string{ 510 "cpe:2.3:a:cloudbees-installation-manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 511 "cpe:2.3:a:cloudbees-installation-manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 512 "cpe:2.3:a:cloudbees-installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 513 "cpe:2.3:a:cloudbees-installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 514 "cpe:2.3:a:cloudbees:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 515 "cpe:2.3:a:cloudbees:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 516 "cpe:2.3:a:cloudbees_installation:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 517 "cpe:2.3:a:cloudbees_installation:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 518 "cpe:2.3:a:cloudbees_installation_manager:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 519 "cpe:2.3:a:cloudbees_installation_manager:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 520 "cpe:2.3:a:jenkins:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 521 "cpe:2.3:a:jenkins:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 522 "cpe:2.3:a:modules:cloudbees-installation-manager:2.89.0.33:*:*:*:*:*:*:*", 523 "cpe:2.3:a:modules:cloudbees_installation_manager:2.89.0.33:*:*:*:*:*:*:*", 524 }, 525 }, 526 { 527 name: "go product and vendor candidates are wired up", 528 p: pkg.Package{ 529 Name: "github.com/someone/something", 530 Version: "3.2", 531 FoundBy: "go-cataloger", 532 Language: pkg.Go, 533 Type: pkg.GoModulePkg, 534 }, 535 expected: []string{ 536 "cpe:2.3:a:someone:something:3.2:*:*:*:*:*:*:*", 537 }, 538 }, 539 { 540 name: "go product with vendor candidates and an extra sub-item", 541 p: pkg.Package{ 542 Name: "github.com/someone/something/more", 543 Version: "3.2", 544 FoundBy: "go-cataloger", 545 Language: pkg.Go, 546 Type: pkg.GoModulePkg, 547 }, 548 expected: []string{ 549 "cpe:2.3:a:someone:something\\/more:3.2:*:*:*:*:*:*:*", 550 }, 551 }, 552 { 553 name: "generate no CPEs for indeterminate golang package name", 554 p: pkg.Package{ 555 Name: "github.com/what", 556 Version: "3.2", 557 FoundBy: "go-cataloger", 558 Language: pkg.Go, 559 Type: pkg.GoModulePkg, 560 }, 561 expected: []string{}, 562 }, 563 { 564 name: "regression: handlebars within java archive", 565 p: pkg.Package{ 566 Name: "handlebars", 567 Version: "3.0.8", 568 Type: pkg.JavaPkg, 569 Language: pkg.Java, 570 FoundBy: "java-cataloger", 571 Metadata: pkg.JavaArchive{ 572 Manifest: &pkg.JavaManifest{ 573 Main: keyValues(map[string]string{ 574 "Extension-Name": "handlebars", 575 "Group-Id": "org.jenkins-ci.ui", 576 "Hudson-Version": "2.204", 577 "Implementation-Title": "handlebars", 578 "Implementation-Version": "3.0.8", 579 "Plugin-Version": "3.0.8", 580 "Short-Name": "handlebars", 581 }), 582 }, 583 PomProperties: &pkg.JavaPomProperties{ 584 GroupID: "org.jenkins-ci.ui", 585 ArtifactID: "handlebars", 586 Version: "3.0.8", 587 }, 588 }, 589 }, 590 expected: []string{ 591 "cpe:2.3:a:handlebars:handlebars:3.0.8:*:*:*:*:*:*:*", 592 "cpe:2.3:a:handlebarsjs:handlebars:3.0.8:*:*:*:*:*:*:*", // important! 593 "cpe:2.3:a:jenkins-ci:handlebars:3.0.8:*:*:*:*:*:*:*", 594 "cpe:2.3:a:jenkins:handlebars:3.0.8:*:*:*:*:*:*:*", 595 "cpe:2.3:a:jenkins_ci:handlebars:3.0.8:*:*:*:*:*:*:*", 596 "cpe:2.3:a:ui:handlebars:3.0.8:*:*:*:*:*:*:*", 597 }, 598 }, 599 { 600 name: "regression: jenkins plugin active-directory", 601 p: pkg.Package{ 602 Name: "active-directory", 603 Version: "2.25.1", 604 Type: pkg.JenkinsPluginPkg, 605 FoundBy: "java-cataloger", 606 Language: pkg.Java, 607 Metadata: pkg.JavaArchive{ 608 Manifest: &pkg.JavaManifest{ 609 Main: keyValues(map[string]string{ 610 "Extension-Name": "active-directory", 611 "Group-Id": "org.jenkins-ci.plugins", 612 }), 613 }, 614 PomProperties: &pkg.JavaPomProperties{ 615 GroupID: "org.jenkins-ci.plugins", 616 ArtifactID: "org.jenkins-ci.plugins", 617 Version: "2.25.1", 618 }, 619 }, 620 }, 621 expected: []string{ 622 "cpe:2.3:a:active-directory:active-directory:2.25.1:*:*:*:*:*:*:*", 623 "cpe:2.3:a:active-directory:active_directory:2.25.1:*:*:*:*:*:*:*", 624 "cpe:2.3:a:active:active-directory:2.25.1:*:*:*:*:*:*:*", 625 "cpe:2.3:a:active:active_directory:2.25.1:*:*:*:*:*:*:*", 626 "cpe:2.3:a:active_directory:active-directory:2.25.1:*:*:*:*:*:*:*", 627 "cpe:2.3:a:active_directory:active_directory:2.25.1:*:*:*:*:*:*:*", 628 "cpe:2.3:a:jenkins-ci:active-directory:2.25.1:*:*:*:*:*:*:*", 629 "cpe:2.3:a:jenkins-ci:active_directory:2.25.1:*:*:*:*:*:*:*", 630 "cpe:2.3:a:jenkins:active-directory:2.25.1:*:*:*:*:*:*:*", // important! 631 "cpe:2.3:a:jenkins:active_directory:2.25.1:*:*:*:*:*:*:*", // important! 632 "cpe:2.3:a:jenkins_ci:active-directory:2.25.1:*:*:*:*:*:*:*", 633 "cpe:2.3:a:jenkins_ci:active_directory:2.25.1:*:*:*:*:*:*:*", 634 }, 635 }, 636 { 637 name: "regression: special characters in CPE should result in no generation", 638 p: pkg.Package{ 639 Name: "bundler", 640 Version: "2.1.4", 641 Type: pkg.GemPkg, 642 FoundBy: "gem-cataloger", 643 Language: pkg.Ruby, 644 Metadata: pkg.RubyGemspec{ 645 Name: "bundler", 646 Version: "2.1.4", 647 Authors: []string{ 648 "jessica lynn suttles", 649 "stephanie morillo", 650 "david rodrÃguez", 651 "andré medeiros", 652 }, 653 }, 654 }, 655 expected: []string{ 656 "cpe:2.3:a:bundler:bundler:2.1.4:*:*:*:*:*:*:*", 657 "cpe:2.3:a:ruby-lang:bundler:2.1.4:*:*:*:*:*:*:*", 658 "cpe:2.3:a:ruby:bundler:2.1.4:*:*:*:*:*:*:*", 659 "cpe:2.3:a:ruby_lang:bundler:2.1.4:*:*:*:*:*:*:*", 660 "cpe:2.3:a:jessica-lynn-suttles:bundler:2.1.4:*:*:*:*:*:*:*", 661 "cpe:2.3:a:jessica_lynn_suttles:bundler:2.1.4:*:*:*:*:*:*:*", 662 "cpe:2.3:a:stephanie-morillo:bundler:2.1.4:*:*:*:*:*:*:*", 663 "cpe:2.3:a:stephanie_morillo:bundler:2.1.4:*:*:*:*:*:*:*", 664 }, 665 }, 666 { 667 name: "regression: python redis shadows normal redis", 668 p: pkg.Package{ 669 Name: "redis", 670 Version: "2.1.4", 671 Type: pkg.PythonPkg, 672 FoundBy: "some-analyzer", 673 Language: pkg.Python, 674 }, 675 expected: []string{ 676 "cpe:2.3:a:python-redis:python-redis:2.1.4:*:*:*:*:*:*:*", 677 "cpe:2.3:a:python-redis:python_redis:2.1.4:*:*:*:*:*:*:*", 678 "cpe:2.3:a:python-redis:redis:2.1.4:*:*:*:*:*:*:*", 679 "cpe:2.3:a:python:python-redis:2.1.4:*:*:*:*:*:*:*", 680 "cpe:2.3:a:python:python_redis:2.1.4:*:*:*:*:*:*:*", 681 "cpe:2.3:a:python:redis:2.1.4:*:*:*:*:*:*:*", 682 "cpe:2.3:a:python_redis:python-redis:2.1.4:*:*:*:*:*:*:*", 683 "cpe:2.3:a:python_redis:python_redis:2.1.4:*:*:*:*:*:*:*", 684 "cpe:2.3:a:python_redis:redis:2.1.4:*:*:*:*:*:*:*", 685 }, 686 }, 687 { 688 name: "regression: ruby-rake apk missing expected ruby-lang:rake CPE", 689 p: pkg.Package{ 690 Name: "ruby-rake", 691 Version: "2.7.6-r0", 692 Type: pkg.ApkPkg, 693 FoundBy: "apk-db-analyzer", 694 Language: pkg.UnknownLanguage, 695 Metadata: pkg.ApkDBEntry{ 696 Package: "ruby-rake", 697 URL: "https://www.ruby-lang.org/", 698 OriginPackage: "ruby", 699 }, 700 }, 701 expected: []string{ 702 "cpe:2.3:a:ruby-lang:rake:2.7.6-r0:*:*:*:*:*:*:*", 703 "cpe:2.3:a:rake:rake:2.7.6-r0:*:*:*:*:*:*:*", 704 "cpe:2.3:a:rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 705 "cpe:2.3:a:rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 706 "cpe:2.3:a:ruby-lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 707 "cpe:2.3:a:ruby-lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 708 "cpe:2.3:a:ruby-rake:rake:2.7.6-r0:*:*:*:*:*:*:*", 709 "cpe:2.3:a:ruby-rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 710 "cpe:2.3:a:ruby-rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 711 "cpe:2.3:a:ruby:rake:2.7.6-r0:*:*:*:*:*:*:*", 712 "cpe:2.3:a:ruby:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 713 "cpe:2.3:a:ruby:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 714 "cpe:2.3:a:ruby_lang:rake:2.7.6-r0:*:*:*:*:*:*:*", 715 "cpe:2.3:a:ruby_lang:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 716 "cpe:2.3:a:ruby_lang:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 717 "cpe:2.3:a:ruby_rake:rake:2.7.6-r0:*:*:*:*:*:*:*", 718 "cpe:2.3:a:ruby_rake:ruby-rake:2.7.6-r0:*:*:*:*:*:*:*", 719 "cpe:2.3:a:ruby_rake:ruby_rake:2.7.6-r0:*:*:*:*:*:*:*", 720 }, 721 }, 722 } 723 724 for _, test := range tests { 725 t.Run(test.name, func(t *testing.T) { 726 actual := FromPackageAttributes(test.p) 727 expectedCpeSet := set.NewStringSet() 728 for _, cpeStr := range test.expected { 729 expectedCpeSet.Add("syft-generated:" + cpeStr) 730 } 731 732 actualCpeSet := set.NewStringSet() 733 for _, a := range actual { 734 actualCpeSet.Add(fmt.Sprintf("%s:%s", a.Source.String(), a.Attributes.String())) 735 } 736 737 extra := strset.Difference(actualCpeSet, expectedCpeSet).List() 738 sort.Strings(extra) 739 if len(extra) > 0 { 740 t.Errorf("found extra CPEs:") 741 for _, d := range extra { 742 t.Logf(" %q,\n", d) 743 } 744 } 745 746 missing := strset.Difference(expectedCpeSet, actualCpeSet).List() 747 sort.Strings(missing) 748 if len(missing) > 0 { 749 t.Errorf("missing CPEs:") 750 for _, d := range missing { 751 t.Logf(" %q,\n", d) 752 } 753 } 754 }) 755 } 756 } 757 758 func TestCandidateProducts(t *testing.T) { 759 tests := []struct { 760 name string 761 p pkg.Package 762 expected []string 763 }{ 764 { 765 name: "apache-cassandra", 766 p: pkg.Package{ 767 Name: "apache-cassandra", 768 Type: pkg.JavaPkg, 769 }, 770 expected: []string{"cassandra" /* <-- known good names | default guess --> */, "apache-cassandra", "apache_cassandra"}, 771 }, 772 { 773 name: "springframework", 774 p: pkg.Package{ 775 Name: "springframework", 776 Type: pkg.JavaPkg, 777 }, 778 expected: []string{"spring_framework", "springsource_spring_framework" /* <-- known good names | default guess --> */, "springframework"}, 779 }, 780 { 781 name: "spring-security-core", 782 p: pkg.Package{ 783 Name: "spring-security-core", 784 Type: pkg.JavaPkg, 785 }, 786 expected: []string{"spring-security-core", "spring_security", "spring_security_core"}, 787 }, 788 { 789 name: "java", 790 p: pkg.Package{ 791 Name: "some-java-package-with-group-id", 792 Type: pkg.JavaPkg, 793 Language: pkg.Java, 794 Metadata: pkg.JavaArchive{ 795 PomProperties: &pkg.JavaPomProperties{ 796 GroupID: "com.apple.itunes", 797 }, 798 }, 799 }, 800 expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"}, 801 }, 802 { 803 name: "java-with-asterisk", 804 p: pkg.Package{ 805 Name: "some-java-package-with-group-id", 806 Type: pkg.JavaPkg, 807 Language: pkg.Java, 808 Metadata: pkg.JavaArchive{ 809 PomProperties: &pkg.JavaPomProperties{ 810 GroupID: "com.apple.itunes.*", 811 }, 812 }, 813 }, 814 expected: []string{"itunes", "some-java-package-with-group-id", "some_java_package_with_group_id"}, 815 }, 816 { 817 name: "jenkins-plugin", 818 p: pkg.Package{ 819 Name: "some-jenkins-plugin", 820 Type: pkg.JenkinsPluginPkg, 821 Language: pkg.Java, 822 Metadata: pkg.JavaArchive{ 823 PomProperties: &pkg.JavaPomProperties{ 824 GroupID: "com.cloudbees.jenkins.plugins", 825 }, 826 }, 827 }, 828 expected: []string{"some-jenkins-plugin", "some_jenkins_plugin", "jenkins"}, 829 }, 830 { 831 name: "javascript", 832 p: pkg.Package{ 833 Name: "handlebars.js", 834 Type: pkg.NpmPkg, 835 }, 836 expected: []string{"handlebars" /* <-- known good names | default guess --> */, "handlebars.js"}, 837 }, 838 { 839 name: "gem", 840 p: pkg.Package{ 841 Name: "RedCloth", 842 Type: pkg.GemPkg, 843 }, 844 expected: []string{"redcloth_library" /* <-- known good names | default guess --> */, "RedCloth"}, 845 }, 846 { 847 name: "python", 848 p: pkg.Package{ 849 Name: "python-rrdtool", 850 Type: pkg.PythonPkg, 851 }, 852 expected: []string{"rrdtool" /* <-- known good names | default guess --> */, "python-rrdtool", "python_rrdtool"}, 853 }, 854 } 855 856 for _, test := range tests { 857 t.Run(test.name, func(t *testing.T) { 858 assert.ElementsMatch(t, test.expected, candidateProducts(test.p)) 859 }) 860 } 861 } 862 863 func TestCandidateVendor(t *testing.T) { 864 tests := []struct { 865 name string 866 p pkg.Package 867 expected []string 868 }{ 869 { 870 name: "elasticsearch", 871 p: pkg.Package{ 872 Name: "elasticsearch", 873 Type: pkg.JavaPkg, 874 }, 875 expected: []string{"elastic" /* <-- known good names | default guess --> */, "elasticsearch"}, 876 }, 877 { 878 name: "spring-security", 879 p: pkg.Package{ 880 Name: "spring-security-core", 881 Type: pkg.JavaPkg, 882 }, 883 expected: []string{"vmware" /* <-- known good names | default guess --> */, "spring", "spring-security", "spring-security-core", "spring_security_core", "spring_security"}, 884 }, 885 { 886 name: "log4j", 887 p: pkg.Package{ 888 Name: "log4j", 889 Type: pkg.JavaPkg, 890 }, 891 expected: []string{"apache"}, 892 }, 893 { 894 name: "Django", 895 p: pkg.Package{ 896 Name: "Django", 897 Type: pkg.PythonPkg, 898 }, 899 expected: []string{"djangoproject" /* <-- known good names | default guess --> */, "Django"}, 900 }, 901 } 902 903 for _, test := range tests { 904 t.Run(fmt.Sprintf("%+v %+v", test.p, test.expected), func(t *testing.T) { 905 assert.ElementsMatch(t, test.expected, candidateVendors(test.p)) 906 }) 907 } 908 } 909 910 func Test_generateSubSelections(t *testing.T) { 911 tests := []struct { 912 field string 913 expected []string 914 }{ 915 { 916 field: "jenkins", 917 expected: []string{"jenkins"}, 918 }, 919 { 920 field: "jenkins-ci", 921 expected: []string{"jenkins", "jenkins-ci"}, 922 }, 923 { 924 field: "jenkins--ci", 925 expected: []string{"jenkins", "jenkins-ci"}, 926 }, 927 { 928 field: "jenkins_ci_tools", 929 expected: []string{"jenkins", "jenkins_ci", "jenkins_ci_tools"}, 930 }, 931 { 932 field: "-jenkins", 933 expected: []string{"jenkins"}, 934 }, 935 { 936 field: "jenkins_", 937 expected: []string{"jenkins"}, 938 }, 939 { 940 field: "", 941 expected: nil, 942 }, 943 { 944 field: "-", 945 expected: nil, 946 }, 947 { 948 field: "_", 949 expected: nil, 950 }, 951 } 952 for _, test := range tests { 953 t.Run(test.field, func(t *testing.T) { 954 assert.ElementsMatch(t, test.expected, generateSubSelections(test.field)) 955 }) 956 } 957 } 958 959 func Test_addSeparatorVariations(t *testing.T) { 960 tests := []struct { 961 input []string 962 expected []string 963 }{ 964 { 965 input: []string{"jenkins-ci"}, 966 expected: []string{"jenkins-ci", "jenkins_ci"}, //, "jenkinsci"}, 967 }, 968 { 969 input: []string{"jenkins_ci"}, 970 expected: []string{"jenkins_ci", "jenkins-ci"}, //, "jenkinsci"}, 971 }, 972 { 973 input: []string{"jenkins"}, 974 expected: []string{"jenkins"}, 975 }, 976 { 977 input: []string{"jenkins-ci", "circle-ci"}, 978 expected: []string{"jenkins-ci", "jenkins_ci", "circle-ci", "circle_ci"}, //, "jenkinsci", "circleci"}, 979 }, 980 } 981 for _, test := range tests { 982 t.Run(strings.Join(test.input, ","), func(t *testing.T) { 983 val := newFieldCandidateSet(test.input...) 984 addDelimiterVariations(val) 985 assert.ElementsMatch(t, test.expected, val.values()) 986 }) 987 } 988 } 989 990 func TestDictionaryFindIsWired(t *testing.T) { 991 992 tests := []struct { 993 name string 994 pkg pkg.Package 995 want []cpe.CPE 996 wantExists bool 997 }{ 998 { 999 name: "sanity check that cpe data is wired up", 1000 pkg: pkg.Package{ 1001 Name: "openssl", 1002 Version: "1.0.2k", 1003 Type: pkg.GemPkg, 1004 }, 1005 want: []cpe.CPE{ 1006 cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:*:*:*", cpe.NVDDictionaryLookupSource), 1007 cpe.Must("cpe:2.3:a:ruby-lang:openssl:1.0.2k:*:*:*:*:ruby:*:*", cpe.NVDDictionaryLookupSource), 1008 }, 1009 // without the cpe data wired up, this would be empty (generation also creates cpe:2.3:a:openssl:openssl:1.0.2k:*:*:*:*:*:*:*) 1010 wantExists: true, 1011 }, 1012 } 1013 for _, tt := range tests { 1014 t.Run(tt.name, func(t *testing.T) { 1015 got, gotExists := FromDictionaryFind(tt.pkg) 1016 assert.ElementsMatch(t, tt.want, got) 1017 assert.Equal(t, tt.wantExists, gotExists) 1018 }) 1019 } 1020 }