github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/file/mock_resolver.go (about) 1 package file 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "path" 9 10 "github.com/bmatcuk/doublestar/v4" 11 12 "github.com/anchore/stereoscope/pkg/file" 13 ) 14 15 var _ Resolver = (*MockResolver)(nil) 16 17 // MockResolver implements the FileResolver interface and is intended for use *only in test code*. 18 // It provides an implementation that can resolve local filesystem paths using only a provided discrete list of file 19 // paths, which are typically paths to test fixtures. 20 type MockResolver struct { 21 locations []Location 22 metadata map[Coordinates]Metadata 23 mimeTypeIndex map[string][]Location 24 extension map[string][]Location 25 basename map[string][]Location 26 } 27 28 // NewMockResolverForPaths creates a new MockResolver, where the only resolvable 29 // files are those specified by the supplied paths. 30 func NewMockResolverForPaths(paths ...string) *MockResolver { 31 var locations []Location 32 extension := make(map[string][]Location) 33 basename := make(map[string][]Location) 34 for _, p := range paths { 35 loc := NewLocation(p) 36 locations = append(locations, loc) 37 ext := path.Ext(p) 38 extension[ext] = append(extension[ext], loc) 39 bn := path.Base(p) 40 basename[bn] = append(basename[bn], loc) 41 } 42 43 return &MockResolver{ 44 locations: locations, 45 metadata: make(map[Coordinates]Metadata), 46 extension: extension, 47 basename: basename, 48 } 49 } 50 51 func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]Metadata) *MockResolver { 52 var locations []Location 53 var mimeTypeIndex = make(map[string][]Location) 54 extension := make(map[string][]Location) 55 basename := make(map[string][]Location) 56 for c, m := range metadata { 57 l := NewLocationFromCoordinates(c) 58 locations = append(locations, l) 59 mimeTypeIndex[m.MIMEType] = append(mimeTypeIndex[m.MIMEType], l) 60 ext := path.Ext(l.RealPath) 61 extension[ext] = append(extension[ext], l) 62 bn := path.Base(l.RealPath) 63 basename[bn] = append(basename[bn], l) 64 } 65 66 return &MockResolver{ 67 locations: locations, 68 metadata: metadata, 69 mimeTypeIndex: mimeTypeIndex, 70 extension: extension, 71 basename: basename, 72 } 73 } 74 75 // HasPath indicates if the given path exists in the underlying source. 76 func (r MockResolver) HasPath(path string) bool { 77 for _, l := range r.locations { 78 if l.RealPath == path { 79 return true 80 } 81 } 82 return false 83 } 84 85 // String returns the string representation of the MockResolver. 86 func (r MockResolver) String() string { 87 return fmt.Sprintf("mock:(%s,...)", r.locations[0].RealPath) 88 } 89 90 // FileContentsByLocation fetches file contents for a single location. If the 91 // path does not exist, an error is returned. 92 func (r MockResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { 93 for _, l := range r.locations { 94 if l.Coordinates == location.Coordinates { 95 return os.Open(location.RealPath) 96 } 97 } 98 99 return nil, fmt.Errorf("no file for location: %v", location) 100 } 101 102 // FilesByPath returns all Locations that match the given paths. 103 func (r MockResolver) FilesByPath(paths ...string) ([]Location, error) { 104 var results []Location 105 for _, p := range paths { 106 for _, location := range r.locations { 107 if p == location.RealPath { 108 results = append(results, NewLocation(p)) 109 } 110 } 111 } 112 113 return results, nil 114 } 115 116 // FilesByGlob returns all Locations that match the given path glob pattern. 117 func (r MockResolver) FilesByGlob(patterns ...string) ([]Location, error) { 118 var results []Location 119 for _, pattern := range patterns { 120 for _, location := range r.locations { 121 matches, err := doublestar.Match(pattern, location.RealPath) 122 if err != nil { 123 return nil, err 124 } 125 if matches { 126 results = append(results, location) 127 } 128 } 129 } 130 131 return results, nil 132 } 133 134 // RelativeFileByPath returns a single Location for the given path. 135 func (r MockResolver) RelativeFileByPath(_ Location, path string) *Location { 136 paths, err := r.FilesByPath(path) 137 if err != nil { 138 return nil 139 } 140 141 if len(paths) < 1 { 142 return nil 143 } 144 145 return &paths[0] 146 } 147 148 func (r MockResolver) AllLocations(ctx context.Context) <-chan Location { 149 results := make(chan Location) 150 go func() { 151 defer close(results) 152 for _, l := range r.locations { 153 select { 154 case <-ctx.Done(): 155 return 156 case results <- l: 157 continue 158 } 159 } 160 }() 161 return results 162 } 163 164 func (r MockResolver) FileMetadataByLocation(l Location) (Metadata, error) { 165 info, err := os.Stat(l.RealPath) 166 if err != nil { 167 return Metadata{}, err 168 } 169 170 // other types not supported 171 ty := file.TypeRegular 172 if info.IsDir() { 173 ty = file.TypeDirectory 174 } 175 176 return Metadata{ 177 FileInfo: info, 178 Type: ty, 179 UserID: 0, // not supported 180 GroupID: 0, // not supported 181 }, nil 182 } 183 184 func (r MockResolver) FilesByMIMEType(types ...string) ([]Location, error) { 185 var locations []Location 186 for _, ty := range types { 187 locations = append(r.mimeTypeIndex[ty], locations...) 188 } 189 return locations, nil 190 } 191 192 func (r MockResolver) FilesByExtension(extensions ...string) ([]Location, error) { 193 var results []Location 194 for _, ext := range extensions { 195 results = append(results, r.extension[ext]...) 196 } 197 return results, nil 198 } 199 200 func (r MockResolver) FilesByBasename(filenames ...string) ([]Location, error) { 201 var results []Location 202 for _, filename := range filenames { 203 results = append(results, r.basename[filename]...) 204 } 205 return results, nil 206 } 207 208 func (r MockResolver) FilesByBasenameGlob(_ ...string) ([]Location, error) { 209 // TODO implement me 210 panic("implement me") 211 } 212 213 func (r MockResolver) Write(_ Location, _ io.Reader) error { 214 return nil 215 }