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