github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/pkg/cataloger/internal/pkgtest/observing_resolver.go (about) 1 package pkgtest 2 3 import ( 4 "fmt" 5 "io" 6 "sort" 7 8 "github.com/nextlinux/gosbom/gosbom/file" 9 "github.com/scylladb/go-set/strset" 10 ) 11 12 var _ file.Resolver = (*ObservingResolver)(nil) 13 14 type ObservingResolver struct { 15 decorated file.Resolver 16 pathQueries map[string][]string 17 pathResponses []file.Location 18 contentQueries []file.Location 19 emptyPathResponses map[string][]string 20 } 21 22 func NewObservingResolver(resolver file.Resolver) *ObservingResolver { 23 return &ObservingResolver{ 24 decorated: resolver, 25 pathResponses: make([]file.Location, 0), 26 emptyPathResponses: make(map[string][]string), 27 pathQueries: make(map[string][]string), 28 } 29 } 30 31 // testing helpers... 32 33 func (r *ObservingResolver) ObservedPathQuery(input string) bool { 34 for _, vs := range r.pathQueries { 35 for _, v := range vs { 36 if v == input { 37 return true 38 } 39 } 40 } 41 return false 42 } 43 44 func (r *ObservingResolver) ObservedPathResponses(path string) bool { 45 for _, loc := range r.pathResponses { 46 if loc.RealPath == path { 47 return true 48 } 49 } 50 return false 51 } 52 53 func (r *ObservingResolver) ObservedContentQueries(path string) bool { 54 for _, loc := range r.contentQueries { 55 if loc.RealPath == path { 56 return true 57 } 58 } 59 return false 60 } 61 62 func (r *ObservingResolver) AllContentQueries() []string { 63 observed := strset.New() 64 for _, loc := range r.contentQueries { 65 observed.Add(loc.RealPath) 66 } 67 return observed.List() 68 } 69 70 func (r *ObservingResolver) AllPathQueries() map[string][]string { 71 return r.pathQueries 72 } 73 74 func (r *ObservingResolver) PruneUnfulfilledPathResponses(ignore map[string][]string, ignorePaths ...string) { 75 if ignore == nil { 76 return 77 } 78 // remove any paths that were ignored for specific calls 79 for k, v := range ignore { 80 results := r.emptyPathResponses[k] 81 for _, ig := range v { 82 for i, result := range results { 83 if result == ig { 84 results = append(results[:i], results[i+1:]...) 85 break 86 } 87 } 88 } 89 if len(results) > 0 { 90 r.emptyPathResponses[k] = results 91 } else { 92 delete(r.emptyPathResponses, k) 93 } 94 } 95 96 // remove any paths that were ignored for all calls 97 for _, ig := range ignorePaths { 98 for k, v := range r.emptyPathResponses { 99 for i, result := range v { 100 if result == ig { 101 v = append(v[:i], v[i+1:]...) 102 break 103 } 104 } 105 if len(v) > 0 { 106 r.emptyPathResponses[k] = v 107 } else { 108 delete(r.emptyPathResponses, k) 109 } 110 } 111 } 112 } 113 114 func (r *ObservingResolver) HasUnfulfilledPathRequests() bool { 115 return len(r.emptyPathResponses) > 0 116 } 117 118 func (r *ObservingResolver) PrettyUnfulfilledPathRequests() string { 119 var res string 120 var keys []string 121 122 for k := range r.emptyPathResponses { 123 keys = append(keys, k) 124 } 125 126 sort.Strings(keys) 127 128 for _, k := range keys { 129 res += fmt.Sprintf(" %s: %+v\n", k, r.emptyPathResponses[k]) 130 } 131 return res 132 } 133 134 // For the file path resolver... 135 136 func (r *ObservingResolver) addPathQuery(name string, input ...string) { 137 r.pathQueries[name] = append(r.pathQueries[name], input...) 138 } 139 140 func (r *ObservingResolver) addPathResponse(locs ...file.Location) { 141 r.pathResponses = append(r.pathResponses, locs...) 142 } 143 144 func (r *ObservingResolver) addEmptyPathResponse(name string, locs []file.Location, paths ...string) { 145 if len(locs) == 0 { 146 results := r.emptyPathResponses[name] 147 results = append(results, paths...) 148 r.emptyPathResponses[name] = results 149 } 150 } 151 152 func (r *ObservingResolver) FilesByPath(paths ...string) ([]file.Location, error) { 153 name := "FilesByPath" 154 r.addPathQuery(name, paths...) 155 156 locs, err := r.decorated.FilesByPath(paths...) 157 158 r.addPathResponse(locs...) 159 r.addEmptyPathResponse(name, locs, paths...) 160 return locs, err 161 } 162 163 func (r *ObservingResolver) FilesByGlob(patterns ...string) ([]file.Location, error) { 164 name := "FilesByGlob" 165 r.addPathQuery(name, patterns...) 166 167 locs, err := r.decorated.FilesByGlob(patterns...) 168 169 r.addPathResponse(locs...) 170 r.addEmptyPathResponse(name, locs, patterns...) 171 return locs, err 172 } 173 174 func (r *ObservingResolver) FilesByMIMEType(types ...string) ([]file.Location, error) { 175 name := "FilesByMIMEType" 176 r.addPathQuery(name, types...) 177 178 locs, err := r.decorated.FilesByMIMEType(types...) 179 180 r.addPathResponse(locs...) 181 r.addEmptyPathResponse(name, locs, types...) 182 return locs, err 183 } 184 185 func (r *ObservingResolver) RelativeFileByPath(l file.Location, path string) *file.Location { 186 name := "RelativeFileByPath" 187 r.addPathQuery(name, path) 188 189 loc := r.decorated.RelativeFileByPath(l, path) 190 191 if loc != nil { 192 r.addPathResponse(*loc) 193 } else { 194 results := r.emptyPathResponses[name] 195 results = append(results, path) 196 r.emptyPathResponses[name] = results 197 } 198 return loc 199 } 200 201 // For the content resolver methods... 202 203 func (r *ObservingResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { 204 r.contentQueries = append(r.contentQueries, location) 205 reader, err := r.decorated.FileContentsByLocation(location) 206 return reader, err 207 } 208 209 // For the remaining resolver methods... 210 211 func (r *ObservingResolver) AllLocations() <-chan file.Location { 212 return r.decorated.AllLocations() 213 } 214 215 func (r *ObservingResolver) HasPath(s string) bool { 216 return r.decorated.HasPath(s) 217 } 218 219 func (r *ObservingResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) { 220 return r.decorated.FileMetadataByLocation(location) 221 }