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 }