github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/containers/dockerbaseimage/dockerbaseimage_test.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package dockerbaseimage_test 16 17 import ( 18 "testing" 19 20 "github.com/google/go-cmp/cmp" 21 "github.com/google/go-cmp/cmp/cmpopts" 22 "github.com/google/osv-scalibr/extractor" 23 "github.com/google/osv-scalibr/extractor/filesystem/containers/dockerbaseimage" 24 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 25 "github.com/google/osv-scalibr/inventory" 26 "github.com/google/osv-scalibr/purl" 27 "github.com/google/osv-scalibr/testing/extracttest" 28 ) 29 30 func TestFileRequired(t *testing.T) { 31 extr := dockerbaseimage.New(dockerbaseimage.DefaultConfig()) 32 33 tests := []struct { 34 name string 35 path string 36 want bool 37 }{ 38 { 39 name: "Dockerfile", 40 path: "testdata/Dockerfile", 41 want: true, 42 }, 43 { 44 name: "mixed-case_Dockerfile", 45 path: "testdata/dOcKeRfile", 46 want: true, 47 }, 48 { 49 name: "Dockerfile_with_extension", 50 path: "testdata/Dockerfile.prod", 51 want: true, 52 }, 53 { 54 name: "Dockerfile_extension", 55 path: "testdata/ext.dockerfile", 56 want: true, 57 }, 58 { 59 name: "not_Dockerfile", 60 path: "testdata/pip.conf", 61 want: false, 62 }, 63 } 64 65 for _, tc := range tests { 66 t.Run(tc.name, func(t *testing.T) { 67 isRequired := extr.FileRequired(simplefileapi.New(tc.path, nil)) 68 if isRequired != tc.want { 69 t.Fatalf("FileRequired(%s): got %v, want %v", tc.path, isRequired, tc.want) 70 } 71 }) 72 } 73 } 74 75 func TestExtract(t *testing.T) { 76 tests := []struct { 77 name string 78 path string 79 cfg dockerbaseimage.Config 80 wantPackages []*extractor.Package 81 }{ 82 { 83 name: "single_stage_dockerfile", 84 path: "testdata/dockerfile.single-stage", 85 cfg: dockerbaseimage.DefaultConfig(), 86 wantPackages: []*extractor.Package{ 87 { 88 Name: "nginx", 89 Version: "1.27.4", 90 Locations: []string{"testdata/dockerfile.single-stage"}, 91 PURLType: purl.TypeDocker, 92 }, 93 }, 94 }, 95 { 96 name: "multi_stage_dockerfile", 97 path: "testdata/dockerfile.multi-stage", 98 cfg: dockerbaseimage.DefaultConfig(), 99 wantPackages: []*extractor.Package{ 100 { 101 Name: "nginx", 102 Version: "1.27.4", 103 Locations: []string{"testdata/dockerfile.multi-stage"}, 104 PURLType: purl.TypeDocker, 105 }, 106 { 107 Name: "ubuntu", 108 Version: "latest", 109 Locations: []string{"testdata/dockerfile.multi-stage"}, 110 PURLType: purl.TypeDocker, 111 }, 112 }, 113 }, 114 { 115 name: "parameterized_dockerfile", 116 path: "testdata/dockerfile.parameterized", 117 cfg: dockerbaseimage.DefaultConfig(), 118 wantPackages: []*extractor.Package{ 119 { 120 Name: "nginx", 121 Version: "1.27.4", 122 Locations: []string{"testdata/dockerfile.parameterized"}, 123 PURLType: purl.TypeDocker, 124 }, 125 { 126 Name: "ubuntu", 127 Version: "latest", 128 Locations: []string{"testdata/dockerfile.parameterized"}, 129 PURLType: purl.TypeDocker, 130 }, 131 }, 132 }, 133 { 134 name: "versionless_dockerfile", 135 path: "testdata/dockerfile.no-version", 136 cfg: dockerbaseimage.DefaultConfig(), 137 wantPackages: []*extractor.Package{ 138 { 139 Name: "nginx", 140 Version: "latest", 141 Locations: []string{"testdata/dockerfile.no-version"}, 142 PURLType: purl.TypeDocker, 143 }, 144 }, 145 }, 146 { 147 name: "sha256_version", 148 path: "testdata/dockerfile.hash", 149 cfg: dockerbaseimage.DefaultConfig(), 150 wantPackages: []*extractor.Package{ 151 { 152 Name: "nginx", 153 Version: "sha256:5a271780516b718910041c0993952f14371490216692290d234a9b231d102e1c", 154 Locations: []string{"testdata/dockerfile.hash"}, 155 PURLType: purl.TypeDocker, 156 }, 157 }, 158 }, 159 { 160 name: "scratch layer", 161 path: "testdata/dockerfile.scratch", 162 cfg: dockerbaseimage.DefaultConfig(), 163 wantPackages: nil, 164 }, 165 { 166 name: "larger than size limit", 167 path: "testdata/dockerfile.multi-stage", 168 cfg: dockerbaseimage.Config{MaxFileSizeBytes: 1}, 169 wantPackages: nil, 170 }, 171 } 172 173 for _, tc := range tests { 174 t.Run(tc.name, func(t *testing.T) { 175 extr := dockerbaseimage.New(tc.cfg) 176 177 input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{ 178 Path: tc.path, 179 }) 180 defer extracttest.CloseTestScanInput(t, input) 181 182 got, err := extr.Extract(t.Context(), &input) 183 if err != nil { 184 t.Fatalf("%s.Extract(%q) failed: %v", extr.Name(), tc.path, err) 185 } 186 187 wantInv := inventory.Inventory{Packages: tc.wantPackages} 188 if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 189 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tc.path, diff) 190 } 191 }) 192 } 193 } 194 195 func TestExtract_failures(t *testing.T) { 196 tests := []struct { 197 name string 198 path string 199 }{ 200 { 201 name: "invalid_Dockerfile", 202 path: "testdata/dockerfile.invalid", 203 }, 204 { 205 name: "empty_Dockerfile", 206 path: "testdata/dockerfile.empty", 207 }, 208 } 209 210 for _, tc := range tests { 211 t.Run(tc.name, func(t *testing.T) { 212 extr := dockerbaseimage.New(dockerbaseimage.DefaultConfig()) 213 214 input := extracttest.GenerateScanInputMock(t, extracttest.ScanInputMockConfig{ 215 Path: tc.path, 216 }) 217 defer extracttest.CloseTestScanInput(t, input) 218 219 _, err := extr.Extract(t.Context(), &input) 220 if err == nil { 221 t.Fatalf("Extract(): got nil, want err") 222 } 223 }) 224 } 225 }