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 }