github.com/google/osv-scalibr@v0.4.1/plugin/list/list_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package list_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 cpb "github.com/google/osv-scalibr/binary/proto/config_go_proto" 23 "github.com/google/osv-scalibr/plugin" 24 pl "github.com/google/osv-scalibr/plugin/list" 25 ) 26 27 func TestExtractorNamesUnique(t *testing.T) { 28 all := pl.All(&cpb.PluginConfig{}) 29 names := make(map[string]plugin.Plugin) 30 for _, e := range pl.FilesystemExtractors(all) { 31 if prev, ok := names[e.Name()]; ok { 32 t.Errorf("%q for Extractor %v already used by Extractor: %v", e.Name(), e, prev) 33 } else { 34 names[e.Name()] = e 35 } 36 } 37 for _, e := range pl.StandaloneExtractors(all) { 38 if prev, ok := names[e.Name()]; ok { 39 t.Errorf("%q for Extractor %v already used by Extractor: %v", e.Name(), e, prev) 40 } else { 41 names[e.Name()] = e 42 } 43 } 44 } 45 46 func TestDetectorNamesUnique(t *testing.T) { 47 all := pl.All(&cpb.PluginConfig{}) 48 names := make(map[string]plugin.Plugin) 49 for _, d := range pl.Detectors(all) { 50 if prev, ok := names[d.Name()]; ok { 51 t.Errorf("%q for Detector %v already used by Detector: %v", d.Name(), d, prev) 52 } else { 53 names[d.Name()] = d 54 } 55 } 56 } 57 58 func TestAnnotatorNamesUnique(t *testing.T) { 59 all := pl.All(&cpb.PluginConfig{}) 60 names := make(map[string]plugin.Plugin) 61 for _, a := range pl.Annotators(all) { 62 if prev, ok := names[a.Name()]; ok { 63 t.Errorf("%q for Annotator %v already used by Annotator: %v", a.Name(), a, prev) 64 } else { 65 names[a.Name()] = a 66 } 67 } 68 } 69 70 func TestEnricherNamesUnique(t *testing.T) { 71 all := pl.All(&cpb.PluginConfig{}) 72 names := make(map[string]plugin.Plugin) 73 for _, e := range pl.Enrichers(all) { 74 if prev, ok := names[e.Name()]; ok { 75 t.Errorf("%q for Enricher %v already used by Enricher: %v", e.Name(), e, prev) 76 } else { 77 names[e.Name()] = e 78 } 79 } 80 } 81 82 func TestFromCapabilities(t *testing.T) { 83 capab := &plugin.Capabilities{OS: plugin.OSLinux} 84 want := []string{"os/snap", "weakcredentials/etcshadow"} // Available for Linux 85 dontWant := []string{"os/homebrew", "windows/dismpatch"} // Not available for Linux 86 plugins := pl.FromCapabilities(capab, &cpb.PluginConfig{}) 87 88 for _, w := range want { 89 found := false 90 for _, p := range plugins { 91 if p.Name() == w { 92 found = true 93 break 94 } 95 } 96 if !found { 97 t.Errorf("pl.FromCapabilities(%v): %q not included in results, should be", capab, w) 98 } 99 } 100 for _, dw := range dontWant { 101 for _, p := range plugins { 102 if p.Name() == dw { 103 t.Errorf("pl.FromCapabilities(%v): %q included in results, shouldn't be", capab, dontWant) 104 } 105 } 106 } 107 } 108 109 func TestFromNames(t *testing.T) { 110 testCases := []struct { 111 desc string 112 names []string 113 wantNames []string 114 wantErr error 115 }{ 116 { 117 desc: "Find_all_Plugins_of_a_type", 118 names: []string{"python", "windows", "cis", "vex", "layerdetails"}, 119 wantNames: []string{"python/pdmlock", "python/pipfilelock", "python/poetrylock", "python/pylock", "python/condameta", "python/uvlock", "python/wheelegg", "python/requirements", "python/setup", "windows/dismpatch", "cis/generic-linux/etcpasswdpermissions", "vex/cachedir", "vex/filter", "vex/os-duplicate/apk", "vex/os-duplicate/cos", "vex/os-duplicate/dpkg", "vex/os-duplicate/rpm", "vex/no-executable/dpkg", "baseimage"}, 120 }, 121 { 122 desc: "Remove_duplicates", 123 names: []string{"python", "python"}, 124 wantNames: []string{"python/pdmlock", "python/pipfilelock", "python/poetrylock", "python/pylock", "python/condameta", "python/uvlock", "python/wheelegg", "python/requirements", "python/setup"}, 125 }, 126 { 127 desc: "Nonexistent_plugin", 128 names: []string{"nonexistent"}, 129 wantErr: cmpopts.AnyError, 130 wantNames: []string{}, 131 }, 132 } 133 134 for _, tc := range testCases { 135 t.Run(tc.desc, func(t *testing.T) { 136 got, err := pl.FromNames(tc.names, &cpb.PluginConfig{}) 137 if diff := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); diff != "" { 138 t.Errorf("pl.FromNames(%v) error got diff (-want +got):\n%s", tc.names, diff) 139 } 140 gotNames := []string{} 141 for _, p := range got { 142 gotNames = append(gotNames, p.Name()) 143 } 144 sort := func(p1, p2 string) bool { return p1 < p2 } 145 if diff := cmp.Diff(tc.wantNames, gotNames, cmpopts.SortSlices(sort)); diff != "" { 146 t.Errorf("pl.FromNames(%v): got diff (-want +got):\n%s", tc.names, diff) 147 } 148 }) 149 } 150 } 151 152 func TestFromName(t *testing.T) { 153 testCases := []struct { 154 desc string 155 name string 156 wantName string 157 wantErr error 158 }{ 159 { 160 desc: "Exact_name", 161 name: "govulncheck/binary", 162 wantName: "govulncheck/binary", 163 }, 164 { 165 desc: "Nonexistent_plugin", 166 name: "nonexistent", 167 wantErr: cmpopts.AnyError, 168 }, 169 { 170 desc: "Not_an_exact_name", 171 name: "python", 172 wantErr: cmpopts.AnyError, 173 }, 174 } 175 176 for _, tc := range testCases { 177 t.Run(tc.desc, func(t *testing.T) { 178 got, err := pl.FromName(tc.name, &cpb.PluginConfig{}) 179 if diff := cmp.Diff(tc.wantErr, err, cmpopts.EquateErrors()); diff != "" { 180 t.Errorf("pl.FromName(%v) error got diff (-want +got):\n%s", tc.name, diff) 181 } 182 if err != nil { 183 return 184 } 185 if tc.wantName != got.Name() { 186 t.Errorf("pl.FromName(%s): want %s, got %s", tc.name, tc.wantName, got.Name()) 187 } 188 }) 189 } 190 }