github.com/google/osv-scalibr@v0.4.1/testing/fakeextractor/fake_extractor.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package fakeextractor provides a Extractor implementation to be used in tests.
    16  //
    17  //nolint:plugger // This package contains test only mocks
    18  package fakeextractor
    19  
    20  import (
    21  	"context"
    22  	"errors"
    23  	"path/filepath"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/google/osv-scalibr/extractor"
    27  	"github.com/google/osv-scalibr/extractor/filesystem"
    28  	"github.com/google/osv-scalibr/inventory"
    29  	"github.com/google/osv-scalibr/plugin"
    30  )
    31  
    32  // NamesErr is a list of Package names and an error.
    33  type NamesErr struct {
    34  	Names []string
    35  	Err   error
    36  }
    37  
    38  // fakeExtractor is an Extractor implementation to be used in tests.
    39  type fakeExtractor struct {
    40  	name           string
    41  	version        int
    42  	requiredFiles  map[string]bool
    43  	pathToNamesErr map[string]NamesErr
    44  	dirExtractor   bool
    45  }
    46  
    47  // AllowUnexported is a utility function to be used with cmp.Diff to
    48  // compare structs that contain the fake extractor.
    49  var AllowUnexported = cmp.AllowUnexported(fakeExtractor{})
    50  
    51  // New returns a fake fakeExtractor.
    52  //
    53  // The fakeExtractor returns FileRequired(path) = true for any path in requiredFiles.
    54  // The fakeExtractor returns the package and error from pathToNamesErr given the same path to Extract(...).
    55  func New(name string, version int, requiredFiles []string, pathToNamesErr map[string]NamesErr) filesystem.Extractor {
    56  	rfs := map[string]bool{}
    57  	for _, path := range requiredFiles {
    58  		rfs[path] = true
    59  	}
    60  
    61  	// Maintain non-nil fields to avoid nil pointers on access such as FileRequired(...).
    62  	if len(pathToNamesErr) == 0 {
    63  		pathToNamesErr = map[string]NamesErr{}
    64  	}
    65  
    66  	return &fakeExtractor{
    67  		name:           name,
    68  		version:        version,
    69  		requiredFiles:  rfs,
    70  		pathToNamesErr: pathToNamesErr,
    71  		dirExtractor:   false,
    72  	}
    73  }
    74  
    75  // NewDirExtractor returns a fake fakeExtractor designed for extracting directories.
    76  //
    77  // The fakeExtractor returns FileRequired(path) = true for any path in requiredFiles.
    78  // The fakeExtractor returns the package and error from pathToNamesErr given the same path to Extract(...).
    79  func NewDirExtractor(name string, version int, requiredFiles []string, pathToNamesErr map[string]NamesErr) filesystem.Extractor {
    80  	ext := New(name, version, requiredFiles, pathToNamesErr).(*fakeExtractor)
    81  	ext.dirExtractor = true
    82  	return ext
    83  }
    84  
    85  // Name returns the extractor's name.
    86  func (e *fakeExtractor) Name() string { return e.name }
    87  
    88  // Version returns the extractor's version.
    89  func (e *fakeExtractor) Version() int { return e.version }
    90  
    91  // Requirements returns the extractor's requirements.
    92  func (e *fakeExtractor) Requirements() *plugin.Capabilities {
    93  	return &plugin.Capabilities{
    94  		ExtractFromDirs: e.dirExtractor,
    95  	}
    96  }
    97  
    98  // FileRequired should return true if the file described by path and mode is
    99  // relevant for the extractor.
   100  //
   101  // FileRequired returns true if the path was in requiredFiles and its value is true during
   102  // construction in New(..., requiredFiles, ...) and false otherwise.
   103  // Note: because mapfs forces all paths to slash, we have to align with it here.
   104  func (e *fakeExtractor) FileRequired(api filesystem.FileAPI) bool {
   105  	return e.requiredFiles[filepath.ToSlash(api.Path())]
   106  }
   107  
   108  // Extract extracts package data relevant for the extractor from a given file.
   109  //
   110  // Extract returns the package list and error associated with input.Path from the pathToPackageErr map used
   111  // during construction in NewExtractor(..., pathToPackageErr, ...).
   112  // Note: because mapfs forces all paths to slash, we have to align with it here.
   113  func (e *fakeExtractor) Extract(ctx context.Context, input *filesystem.ScanInput) (inventory.Inventory, error) {
   114  	path := filepath.ToSlash(input.Path)
   115  	namesErr, ok := e.pathToNamesErr[path]
   116  	if !ok {
   117  		return inventory.Inventory{}, errors.New("unrecognized path")
   118  	}
   119  
   120  	pkgs := []*extractor.Package{}
   121  	for _, name := range namesErr.Names {
   122  		pkgs = append(pkgs, &extractor.Package{
   123  			Name:      name,
   124  			Locations: []string{path},
   125  		})
   126  	}
   127  
   128  	return inventory.Inventory{Packages: pkgs}, namesErr.Err
   129  }