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  }