github.com/quay/claircore@v1.5.28/libindex/libindex_integration_test.go (about)

     1  package libindex
     2  
     3  import (
     4  	"context"
     5  	"crypto/sha256"
     6  	"fmt"
     7  	"io"
     8  	"reflect"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/golang/mock/gomock"
    14  	"github.com/google/go-cmp/cmp"
    15  	"github.com/jackc/pgx/v4/pgxpool"
    16  	"github.com/quay/zlog"
    17  
    18  	"github.com/quay/claircore"
    19  	"github.com/quay/claircore/datastore/postgres"
    20  	"github.com/quay/claircore/internal/wart"
    21  	"github.com/quay/claircore/linux"
    22  	"github.com/quay/claircore/pkg/ctxlock"
    23  	"github.com/quay/claircore/test"
    24  	"github.com/quay/claircore/test/integration"
    25  	indexer "github.com/quay/claircore/test/mock/indexer"
    26  	pgtest "github.com/quay/claircore/test/postgres"
    27  	"github.com/quay/claircore/whiteout"
    28  )
    29  
    30  // Testcase is a test case for calling libindex.
    31  type testcase struct {
    32  	// Packages is the number of packages to generate for each layer.
    33  	Packages []int
    34  	// Layers is the number of layers to place in the manifest under test.
    35  	Layers int
    36  	// Scanners is the number of mock scanners to create. Must be at least 1.
    37  	Scanners int
    38  }
    39  
    40  // Name returns a unique name for the test
    41  func (tc testcase) Name() string {
    42  	var b strings.Builder
    43  	for i, n := range tc.Packages {
    44  		if i != 0 {
    45  			b.WriteByte(':')
    46  		}
    47  		fmt.Fprint(&b, n)
    48  	}
    49  	return fmt.Sprintf("%dlayer_%spackage_%dscanner", tc.Layers, b.String(), tc.Scanners)
    50  }
    51  
    52  // Digest returns a sham digest, for use in a manifest.
    53  func (tc testcase) Digest() claircore.Digest {
    54  	h := sha256.New()
    55  	io.WriteString(h, tc.Name())
    56  	d, err := claircore.NewDigest("sha256", h.Sum(nil))
    57  	if err != nil {
    58  		panic(err)
    59  	}
    60  	return d
    61  }
    62  
    63  // RunInner "exposes" just the test logic.
    64  func (tc testcase) RunInner(ctx context.Context, t *testing.T, pool *pgxpool.Pool, next checkFunc) {
    65  	ms := []*indexer.MockPackageScanner{}
    66  	ctrl := gomock.NewController(t)
    67  
    68  	// create the desired number of package scanners. we will
    69  	// configure the Scan() method on the mock when generated layers below
    70  	for i := 0; i < tc.Scanners; i++ {
    71  		m := indexer.NewMockPackageScanner(ctrl)
    72  		m.EXPECT().Name().AnyTimes().Return(fmt.Sprintf("test-scanner-%d", i))
    73  		m.EXPECT().Version().AnyTimes().Return("v0.0.1")
    74  		m.EXPECT().Kind().AnyTimes().Return("package")
    75  		ms = append(ms, m)
    76  	}
    77  
    78  	// configure scanners to return the desired pkg counts
    79  	for i := 0; i < tc.Layers; i++ {
    80  		// generate the desired number of package we'll return for this layer
    81  		pkgs := test.GenUniquePackages(tc.Packages[i])
    82  
    83  		// configure the desired scanners to return this set of pkgs when their Scan()
    84  		// are called.
    85  		for _, m := range ms {
    86  			m.EXPECT().Scan(gomock.Any(), gomock.Any()).Return(pkgs, nil)
    87  		}
    88  	}
    89  	c, descs := test.ServeLayers(t, tc.Layers)
    90  
    91  	// create manifest
    92  	m := &claircore.Manifest{
    93  		Hash:   tc.Digest(),
    94  		Layers: wart.DescriptionsToLayers(descs),
    95  	}
    96  
    97  	store, err := postgres.InitPostgresIndexerStore(ctx, pool, false)
    98  	if err != nil {
    99  		t.Fatalf("failed to create postgres connection: %v", err)
   100  	}
   101  
   102  	ctxLocker, err := ctxlock.New(ctx, pool)
   103  	if err != nil {
   104  		t.Fatalf("failed to create context locker: %v", err)
   105  	}
   106  
   107  	// create libindex instance
   108  	opts := &Options{
   109  		Store:                store,
   110  		Locker:               ctxLocker,
   111  		FetchArena:           NewRemoteFetchArena(c, t.TempDir()),
   112  		ScanLockRetry:        2 * time.Second,
   113  		LayerScanConcurrency: 1,
   114  		Ecosystems: []*indexer.Ecosystem{
   115  			{
   116  				PackageScanners: func(_ context.Context) ([]indexer.PackageScanner, error) {
   117  					ps := make([]indexer.PackageScanner, len(ms))
   118  					for i := range ms {
   119  						ps[i] = ms[i]
   120  					}
   121  					return ps, nil
   122  				},
   123  				DistributionScanners: func(_ context.Context) ([]indexer.DistributionScanner, error) {
   124  					return nil, nil
   125  				},
   126  				RepositoryScanners: func(_ context.Context) ([]indexer.RepositoryScanner, error) {
   127  					return nil, nil
   128  				},
   129  				FileScanners: func(ctx context.Context) ([]indexer.FileScanner, error) {
   130  					return []indexer.FileScanner{&whiteout.Scanner{}}, nil
   131  				},
   132  				Coalescer: func(_ context.Context) (indexer.Coalescer, error) {
   133  					return linux.NewCoalescer(), nil
   134  				},
   135  				Name: "test",
   136  			},
   137  		},
   138  	}
   139  
   140  	lib, err := New(ctx, opts, c)
   141  	if err != nil {
   142  		t.Fatalf("failed to create libindex instance: %v", err)
   143  	}
   144  	defer lib.Close(ctx)
   145  
   146  	// setup scan and run
   147  	ir, err := lib.Index(ctx, m)
   148  	if err != nil {
   149  		t.Fatalf("failed to scan manifest: %v", err)
   150  	}
   151  
   152  	next(ctx, t, tc, lib, ir)
   153  }
   154  
   155  // Run does per-test setup and calls RunInner
   156  func (tc testcase) Run(ctx context.Context, check checkFunc) func(*testing.T) {
   157  	return func(t *testing.T) {
   158  		t.Parallel()
   159  		integration.NeedDB(t)
   160  		ctx := zlog.Test(ctx, t)
   161  		pool := pgtest.TestIndexerDB(ctx, t)
   162  		tc.RunInner(ctx, t, pool, check)
   163  	}
   164  }
   165  
   166  // CheckFunc is used by a testcase to check the result after the generic setup.
   167  type checkFunc func(context.Context, *testing.T, testcase, *Libindex, *claircore.IndexReport)
   168  
   169  // CheckEqual is a checkFunc that does what it says on the tin.
   170  func checkEqual(ctx context.Context, t *testing.T, tc testcase, lib *Libindex, ir *claircore.IndexReport) {
   171  	// BUG(hank) The cached and live results of an index report are different,
   172  	// because of the JSON marshaling. This should not be the case.
   173  	cmpopts := cmp.Options{
   174  		cmp.AllowUnexported(claircore.Digest{}),
   175  		cmp.FilterPath(func(p cmp.Path) bool {
   176  			s := p.Index(-3)
   177  			m := p.Last().String()
   178  			return m == ".Files" || (s.Type() == reflect.TypeOf((*claircore.Package)(nil)) &&
   179  				(m == ".RepositoryHint" || m == ".PackageDB"))
   180  		}, cmp.Ignore()),
   181  	}
   182  	hash := tc.Digest()
   183  	if got, want := ir.Hash, hash; !cmp.Equal(got, want, cmpopts) {
   184  		t.Error(cmp.Diff(got, want, cmpopts))
   185  	}
   186  	if !ir.Success {
   187  		t.Error("expected Success in IndexReport")
   188  	}
   189  
   190  	// confirm scan report retrieved from libindex matches the one
   191  	// the Scan() method returned
   192  	want := ir
   193  	got, ok, err := lib.IndexReport(ctx, hash)
   194  	if err != nil {
   195  		t.Error(err)
   196  	}
   197  	if !ok {
   198  		t.Error("expected ok return from IndexReport")
   199  	}
   200  	if !cmp.Equal(got, want, cmpopts) {
   201  		t.Error(cmp.Diff(got, want, cmpopts))
   202  	}
   203  }
   204  
   205  var testtable = []testcase{
   206  	{
   207  		Layers:   1,
   208  		Packages: []int{1},
   209  		Scanners: 1,
   210  	},
   211  	{
   212  		Layers:   1,
   213  		Packages: []int{2},
   214  		Scanners: 2,
   215  	},
   216  	{
   217  		Layers:   2,
   218  		Packages: []int{1, 1},
   219  		Scanners: 1,
   220  	},
   221  	{
   222  		Layers:   2,
   223  		Packages: []int{1, 1},
   224  		Scanners: 2,
   225  	},
   226  	{
   227  		Layers:   2,
   228  		Packages: []int{2, 2},
   229  		Scanners: 2,
   230  	},
   231  	{
   232  		Layers:   3,
   233  		Packages: []int{1, 1, 1},
   234  		Scanners: 1,
   235  	},
   236  	{
   237  		Layers:   3,
   238  		Packages: []int{2, 2, 2},
   239  		Scanners: 2,
   240  	},
   241  	{
   242  		Layers:   3,
   243  		Packages: []int{3, 3, 3},
   244  		Scanners: 3,
   245  	},
   246  }
   247  
   248  // TestIndex tests that our library performs a successful scan.
   249  // we mock out the package scanners to return sets of packages generated by
   250  // test functions.
   251  func TestIndex(t *testing.T) {
   252  	ctx := context.Background()
   253  	for _, tc := range testtable {
   254  		t.Run(tc.Name(), tc.Run(ctx, checkEqual))
   255  	}
   256  }