github.com/quay/claircore@v1.5.28/test/packagescanner.go (about)

     1  package test
     2  
     3  import (
     4  	"context"
     5  	"sort"
     6  	"strings"
     7  	"testing"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	"github.com/quay/zlog"
    11  
    12  	"github.com/quay/claircore"
    13  	"github.com/quay/claircore/indexer"
    14  	"github.com/quay/claircore/test/fetch"
    15  )
    16  
    17  // ScannerTestcase can be used for testing layers found in the wild against a
    18  // scanner.
    19  //
    20  // Tests that use this struct should not be marked as integration tests, as the
    21  // Run method does that internally if it needs to talk to the network.
    22  type ScannerTestcase struct {
    23  	Domain  string
    24  	Name    string
    25  	Hash    string
    26  	Want    []*claircore.Package
    27  	Scanner indexer.PackageScanner
    28  }
    29  
    30  // Digest reports the digest in the Hash member.
    31  //
    32  // Panics if an error is returned from ParseDigest.
    33  func (tc ScannerTestcase) Digest() claircore.Digest {
    34  	d, err := claircore.ParseDigest(tc.Hash)
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  	return d
    39  }
    40  
    41  // Run returns a function suitable for using with (*testing.T).Run.
    42  //
    43  // This function assumes the returned values must exactly match tc.Want.
    44  // If tc.Want only covers a subset of potential returned values, then
    45  // use RunSubset.
    46  func (tc ScannerTestcase) Run(ctx context.Context) func(*testing.T) {
    47  	sort.Slice(tc.Want, pkgSort(tc.Want))
    48  	return func(t *testing.T) {
    49  		ctx := zlog.Test(ctx, t)
    50  		l := tc.getLayer(ctx, t)
    51  
    52  		got, err := tc.Scanner.Scan(ctx, l)
    53  		if err != nil {
    54  			t.Fatal(err)
    55  		}
    56  		sort.Slice(got, pkgSort(got))
    57  		t.Logf("found %d packages", len(got))
    58  		if !cmp.Equal(tc.Want, got) {
    59  			t.Error(cmp.Diff(tc.Want, got))
    60  		}
    61  	}
    62  }
    63  
    64  // RunSubset returns a function suitable for using with (*testing.T).Run.
    65  //
    66  // This function is similar to except it assumes tc.Want is a subset of
    67  // all potential values.
    68  // n is the total number of expected packages, ie len(got).
    69  func (tc ScannerTestcase) RunSubset(ctx context.Context, n int) func(*testing.T) {
    70  	sort.Slice(tc.Want, pkgSort(tc.Want))
    71  	return func(t *testing.T) {
    72  		ctx := zlog.Test(ctx, t)
    73  		l := tc.getLayer(ctx, t)
    74  
    75  		got, err := tc.Scanner.Scan(ctx, l)
    76  		if err != nil {
    77  			t.Fatal(err)
    78  		}
    79  
    80  		t.Logf("found %d packages", len(got))
    81  		if !cmp.Equal(n, len(got)) {
    82  			t.Error(cmp.Diff(n, len(got)))
    83  		}
    84  
    85  		type key struct {
    86  			name, hint string
    87  		}
    88  		gotMap := make(map[key]*claircore.Package, len(got))
    89  		for _, p := range got {
    90  			gotMap[key{
    91  				name: p.Name,
    92  				hint: p.RepositoryHint,
    93  			}] = p
    94  		}
    95  
    96  		for _, p := range tc.Want {
    97  			g, exists := gotMap[key{
    98  				name: p.Name,
    99  				hint: p.RepositoryHint,
   100  			}]
   101  			if !exists {
   102  				t.Errorf("\"got\" is missing package %s with hint %s", p.Name, p.RepositoryHint)
   103  				continue
   104  			}
   105  
   106  			if !cmp.Equal(p, g) {
   107  				t.Error(cmp.Diff(p, g))
   108  			}
   109  		}
   110  	}
   111  }
   112  
   113  func (tc *ScannerTestcase) getLayer(ctx context.Context, t *testing.T) *claircore.Layer {
   114  	d := tc.Digest()
   115  	n, err := fetch.Layer(ctx, t, tc.Domain, tc.Name, d)
   116  	if err != nil {
   117  		t.Fatal(err)
   118  	}
   119  	t.Cleanup(func() {
   120  		if err := n.Close(); err != nil {
   121  			t.Errorf("closing %q: %v", n.Name(), err)
   122  		}
   123  	})
   124  	desc := claircore.LayerDescription{
   125  		URI:    "file:///dev/null",
   126  		Digest: d.String(),
   127  		// Bit of bad coupling seeping in here: all tar-based layers
   128  		// are handled the same, so this doesn't matter as long as
   129  		// it's a tar.
   130  		MediaType: MediaType,
   131  	}
   132  	var l claircore.Layer
   133  	if err := l.Init(ctx, &desc, n); err != nil {
   134  		t.Fatal(err)
   135  	}
   136  	t.Cleanup(func() {
   137  		if err := l.Close(); err != nil {
   138  			t.Errorf("closing Layer %q: %v", l.Hash, err)
   139  		}
   140  	})
   141  
   142  	return &l
   143  }
   144  
   145  func pkgSort(s []*claircore.Package) func(i, j int) bool {
   146  	return func(i, j int) bool {
   147  		switch strings.Compare(s[i].Name, s[j].Name) {
   148  		case -1:
   149  			return true
   150  		case 0:
   151  			return strings.Compare(s[i].RepositoryHint, s[j].RepositoryHint) == -1
   152  		default:
   153  		}
   154  		return false
   155  	}
   156  }