github.com/quay/claircore@v1.5.28/indexer/controller/scanlayers_test.go (about) 1 package controller 2 3 import ( 4 "context" 5 "crypto/sha256" 6 "fmt" 7 "testing" 8 9 "github.com/golang/mock/gomock" 10 "github.com/quay/zlog" 11 12 "github.com/quay/claircore" 13 "github.com/quay/claircore/indexer" 14 "github.com/quay/claircore/internal/wart" 15 "github.com/quay/claircore/test" 16 indexer_mock "github.com/quay/claircore/test/mock/indexer" 17 ) 18 19 func TestScanLayers(t *testing.T) { 20 ctx := context.Background() 21 tt := []struct { 22 mock func(t *testing.T) indexer.Store 23 name string 24 expectedState State 25 }{ 26 { 27 name: "Success", 28 expectedState: Coalesce, 29 mock: func(t *testing.T) indexer.Store { 30 ctrl := gomock.NewController(t) 31 s := indexer_mock.NewMockStore(ctrl) 32 33 s.EXPECT().LayerScanned(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(true, nil) 34 return s 35 }, 36 }, 37 } 38 39 for _, table := range tt { 40 t.Run(table.name, func(t *testing.T) { 41 ctx := zlog.Test(ctx, t) 42 s := table.mock(t) 43 opts := &indexer.Options{ 44 Store: s, 45 } 46 scnr := New(opts) 47 var err error 48 scnr.LayerScanner, err = indexer.NewLayerScanner(ctx, 1, opts) 49 if err != nil { 50 t.Error(err) 51 } 52 53 state, err := scanLayers(ctx, scnr) 54 if err != nil { 55 t.Error(err) 56 } 57 if got, want := state, table.expectedState; got != want { 58 t.Errorf("got: %v, want: %v", got, want) 59 } 60 }) 61 } 62 } 63 64 func TestScanNoErrors(t *testing.T) { 65 ctx := zlog.Test(context.Background(), t) 66 ctrl := gomock.NewController(t) 67 68 store := indexer_mock.NewMockStore(ctrl) 69 pkg := indexer_mock.NewMockPackageScanner(ctrl) 70 dist := indexer_mock.NewMockDistributionScanner(ctrl) 71 repo := indexer_mock.NewMockRepositoryScanner(ctrl) 72 _, descs := test.ServeLayers(t, 2) 73 descMatch := make([]*test.LayerMatcher, len(descs)) 74 for i := range descs { 75 descMatch[i] = test.NewLayerMatcher(&descs[i]) 76 } 77 78 // These type parameters are needed for go1.20. 79 setupCalls[*indexer_mock.MockPackageScannerMockRecorder, *indexer_mock.MockStoreMockRecorder](pkg, store, descMatch) 80 setupCalls[*indexer_mock.MockDistributionScannerMockRecorder, *indexer_mock.MockStoreMockRecorder](dist, store, descMatch) 81 setupCalls[*indexer_mock.MockRepositoryScannerMockRecorder, *indexer_mock.MockStoreMockRecorder](repo, store, descMatch) 82 83 ecosystem := &indexer.Ecosystem{ 84 Name: "test-ecosystem", 85 PackageScanners: func(ctx context.Context) ([]indexer.PackageScanner, error) { 86 return []indexer.PackageScanner{pkg}, nil 87 }, 88 DistributionScanners: func(ctx context.Context) ([]indexer.DistributionScanner, error) { 89 return []indexer.DistributionScanner{dist}, nil 90 }, 91 RepositoryScanners: func(ctx context.Context) ([]indexer.RepositoryScanner, error) { 92 return []indexer.RepositoryScanner{repo}, nil 93 }, 94 } 95 96 ls := wart.DescriptionsToLayers(descs) 97 d, err := claircore.NewDigest("sha256", make([]byte, sha256.Size)) 98 if err != nil { 99 t.Fatal(err) 100 } 101 m := &claircore.Manifest{ 102 Hash: d, 103 Layers: ls, 104 } 105 106 opts := &indexer.Options{ 107 Store: store, 108 Ecosystems: []*indexer.Ecosystem{ecosystem}, 109 } 110 c := New(opts) 111 c.manifest = m 112 c.LayerScanner, err = indexer.NewLayerScanner(ctx, 1, opts) 113 if err != nil { 114 t.Error(err) 115 } 116 117 state, err := scanLayers(ctx, c) 118 if err != nil { 119 t.Fatalf("failed to scan test layers: %v", err) 120 } 121 122 if state != Coalesce { 123 t.Errorf("got: %v state, wanted: %v state", state, Coalesce) 124 } 125 } 126 127 type scanRecorder interface { 128 Kind() *gomock.Call 129 Name() *gomock.Call 130 Version() *gomock.Call 131 // Abuse the fact that we overloaded the "Scan" name. That's finally useful. 132 Scan(any, any) *gomock.Call 133 } 134 135 type storeRecorder interface { 136 LayerScanned(any, any, any) *gomock.Call 137 SetLayerScanned(any, any, any) *gomock.Call 138 IndexPackages(any, any, any, any) *gomock.Call 139 IndexDistributions(any, any, any, any) *gomock.Call 140 IndexRepositories(any, any, any, any) *gomock.Call 141 } 142 143 type mock[R any] interface { 144 EXPECT() R 145 } 146 147 // SetupCalls is a helper for doing all the setup for a VersionedScanner mock. 148 func setupCalls[C scanRecorder, T storeRecorder, Mc mock[C], Mt mock[T]](m Mc, s Mt, ls []*test.LayerMatcher) Mc { 149 // In hindsight, this function gains little from being written with 150 // generics. 151 var retVal any 152 var kind string 153 scan := m.EXPECT() 154 store := s.EXPECT() 155 switch t := any(m).(type) { 156 case *indexer_mock.MockPackageScanner: 157 retVal = []*claircore.Package{} 158 kind = "package" 159 case *indexer_mock.MockDistributionScanner: 160 retVal = []*claircore.Distribution{} 161 kind = "distribution" 162 case *indexer_mock.MockRepositoryScanner: 163 retVal = []*claircore.Repository{} 164 kind = "repository" 165 default: 166 panic(fmt.Sprintf("unreachable: passed %T", t)) 167 } 168 for _, l := range ls { 169 scan.Scan(gomock.Any(), l).Return(retVal, nil) 170 d := l.DigestMatcher() 171 store.LayerScanned(gomock.Any(), d, m).Return(false, nil) 172 store.SetLayerScanned(gomock.Any(), d, m).Return(nil) 173 switch t := any(m).(type) { 174 case *indexer_mock.MockPackageScanner: 175 store.IndexPackages(gomock.Any(), gomock.Any(), l, m).Return(nil) 176 case *indexer_mock.MockDistributionScanner: 177 store.IndexDistributions(gomock.Any(), gomock.Any(), l, m).Return(nil) 178 case *indexer_mock.MockRepositoryScanner: 179 store.IndexRepositories(gomock.Any(), gomock.Any(), l, m).Return(nil) 180 default: 181 panic(fmt.Sprintf("unreachable: passed %T", t)) 182 } 183 } 184 scan.Kind().MinTimes(1).Return(kind) 185 scan.Name().AnyTimes().Return(kind) 186 scan.Version().AnyTimes().Return("1") 187 return m 188 }