github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/internal/pkgtest/observing_resolver.go (about)

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