github.com/quay/claircore@v1.5.28/datastore/postgres/gc_test.go (about) 1 package postgres 2 3 import ( 4 "context" 5 "crypto/rand" 6 "encoding/hex" 7 "io" 8 "net/http" 9 "testing" 10 11 "github.com/quay/zlog" 12 13 "github.com/quay/claircore" 14 "github.com/quay/claircore/libvuln/driver" 15 "github.com/quay/claircore/libvuln/updates" 16 "github.com/quay/claircore/pkg/ctxlock" 17 "github.com/quay/claircore/test" 18 "github.com/quay/claircore/test/integration" 19 pgtest "github.com/quay/claircore/test/postgres" 20 ) 21 22 type updaterMock struct { 23 _name func() string 24 _fetch func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) 25 _parse func(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error) 26 } 27 28 func (u *updaterMock) Name() string { 29 return u._name() 30 } 31 32 func (u *updaterMock) Fetch(ctx context.Context, fp driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { 33 return u._fetch(ctx, fp) 34 } 35 36 func (u *updaterMock) Parse(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error) { 37 return u._parse(ctx, contents) 38 } 39 40 // TestGC confirms the garbage collection of 41 // vulnerabilities works correctly. 42 func TestGC(t *testing.T) { 43 integration.NeedDB(t) 44 45 // mock returns exactly one random vuln each time its Parse method is called. 46 // each update operation will be associated with a single vuln. 47 mock := &updaterMock{ 48 _name: func() string { return "MockUpdater" }, 49 _fetch: func(_ context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) { 50 return nil, "", nil 51 }, 52 _parse: func(ctx context.Context, contents io.ReadCloser) ([]*claircore.Vulnerability, error) { 53 return []*claircore.Vulnerability{ 54 { 55 Name: randString(t), 56 Updater: "MockUpdater", 57 Package: test.GenUniquePackages(1)[0], 58 }, 59 }, nil 60 }, 61 } 62 63 // these tests maintain a one:one relationship between 64 // update operations and a linked vulnerability for simplicty. 65 // in other words, each update operation inserts one vuln and 66 // each deletion of an update operation should induce a deletion 67 // of one vuln. 68 table := []struct { 69 // name of test case 70 name string 71 // number of update operations to create 72 updateOps int 73 // number of update operations to keep 74 keep int 75 }{ 76 { 77 "Small", 78 4, 79 3, 80 }, 81 { 82 "Large", 83 100, 84 50, 85 }, 86 { 87 "Odd", 88 37, 89 23, 90 }, 91 { 92 "Inversed", 93 10, 94 50, 95 }, 96 { 97 "Throttle", 98 60, 99 5, 100 }, 101 } 102 103 for _, tt := range table { 104 t.Run(tt.name, func(t *testing.T) { 105 ctx := zlog.Test(context.Background(), t) 106 pool := pgtest.TestMatcherDB(ctx, t) 107 store := NewMatcherStore(pool) 108 locks, err := ctxlock.New(ctx, pool) 109 if err != nil { 110 t.Error(err) 111 } 112 defer locks.Close(ctx) 113 mgr, err := updates.NewManager( 114 ctx, 115 NewMatcherStore(pool), 116 locks, 117 http.DefaultClient, // Used on purpose -- shouldn't actually get called by anything. 118 updates.WithEnabled([]string{}), 119 updates.WithOutOfTree([]driver.Updater{mock}), 120 ) 121 if err != nil { 122 t.Fatalf("failed creating update manager: %v", err) 123 } 124 125 // run updater n times to create n update operations 126 for i := 0; i < tt.updateOps; i++ { 127 err := mgr.Run(ctx) 128 if err != nil { 129 t.Fatalf("manager failed to run: %v", err) 130 } 131 } 132 133 // confirm update operations exist 134 ops, err := store.GetUpdateOperations(ctx, driver.VulnerabilityKind) 135 if err != nil { 136 t.Fatalf("failed obtaining update ops: %v", err) 137 } 138 if len(ops["MockUpdater"]) != tt.updateOps { 139 t.Fatalf("%s got: %v want: %v", tt.name, len(ops["MockUpdater"]), tt.updateOps) 140 } 141 142 // run gc 143 expectedNotDone := Max(tt.updateOps-tt.keep-GCThrottle, 0) 144 notDone, err := store.GC(ctx, tt.keep) 145 switch { 146 case err != nil: 147 t.Fatalf("error while performing GC: %v", err) 148 case notDone != int64(expectedNotDone): 149 t.Fatalf("%s got: %v, want: %v", tt.name, notDone, expectedNotDone) 150 } 151 152 wantKeep := tt.keep 153 if tt.updateOps < tt.keep { 154 wantKeep = tt.updateOps 155 } 156 ops, err = store.GetUpdateOperations(ctx, driver.VulnerabilityKind) 157 if err != nil { 158 t.Fatalf("failed obtaining update ops: %v", err) 159 } 160 t.Logf("ops %v", ops) 161 expectedRemaining := wantKeep + expectedNotDone 162 if len(ops["MockUpdater"]) != expectedRemaining { 163 t.Fatalf("%s got: %v want: %v", tt.name, len(ops["MockUpdater"]), expectedRemaining) 164 } 165 }) 166 } 167 } 168 169 func randString(t *testing.T) string { 170 buf := make([]byte, 4, 4) 171 _, err := io.ReadAtLeast(rand.Reader, buf, len(buf)) 172 if err != nil { 173 t.Fatalf("failed to generate random string: %v", err) 174 } 175 return hex.EncodeToString(buf) 176 } 177 178 func Max(x, y int) int { 179 if x < y { 180 return y 181 } 182 return x 183 }