github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/os/kernel/vmlinuz/vmlinuz_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 vmlinuz_test 16 17 import ( 18 "io/fs" 19 "os" 20 "path/filepath" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/osv-scalibr/extractor" 25 "github.com/google/osv-scalibr/extractor/filesystem" 26 "github.com/google/osv-scalibr/extractor/filesystem/internal/units" 27 "github.com/google/osv-scalibr/extractor/filesystem/os/kernel/vmlinuz" 28 vmlinuzmeta "github.com/google/osv-scalibr/extractor/filesystem/os/kernel/vmlinuz/metadata" 29 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 30 scalibrfs "github.com/google/osv-scalibr/fs" 31 "github.com/google/osv-scalibr/inventory" 32 "github.com/google/osv-scalibr/stats" 33 "github.com/google/osv-scalibr/testing/fakefs" 34 "github.com/google/osv-scalibr/testing/testcollector" 35 ) 36 37 func TestNew(t *testing.T) { 38 tests := []struct { 39 name string 40 cfg vmlinuz.Config 41 wantCfg vmlinuz.Config 42 }{ 43 { 44 name: "default", 45 cfg: vmlinuz.DefaultConfig(), 46 wantCfg: vmlinuz.Config{ 47 MaxFileSizeBytes: 30 * units.MiB, 48 }, 49 }, 50 { 51 name: "custom", 52 cfg: vmlinuz.Config{ 53 MaxFileSizeBytes: 10, 54 }, 55 wantCfg: vmlinuz.Config{ 56 MaxFileSizeBytes: 10, 57 }, 58 }, 59 } 60 61 for _, tt := range tests { 62 t.Run(tt.name, func(t *testing.T) { 63 got := vmlinuz.New(tt.cfg) 64 if diff := cmp.Diff(tt.wantCfg, got.Config()); diff != "" { 65 t.Errorf("New(%+v).Config(): (-want +got):\n%s", tt.cfg, diff) 66 } 67 }) 68 } 69 } 70 71 func TestFileRequired(t *testing.T) { 72 tests := []struct { 73 name string 74 path string 75 fileSizeBytes int64 76 maxFileSizeBytes int64 77 wantRequired bool 78 wantResultMetric stats.FileRequiredResult 79 }{ 80 { 81 name: "required vmlinuz file", 82 path: "boot/foo/vmlinuz", 83 wantRequired: true, 84 wantResultMetric: stats.FileRequiredResultOK, 85 }, 86 { 87 name: "required vmlinuz-* file", 88 path: "boot/foo/voo/zoo/vmlinuz-x.y.z", 89 wantRequired: true, 90 wantResultMetric: stats.FileRequiredResultOK, 91 }, 92 { 93 name: "file required if file size < max file size", 94 path: "boot/foo/voo/zoo/vmlinuz", 95 fileSizeBytes: 100 * units.KiB, 96 maxFileSizeBytes: 1000 * units.KiB, 97 wantRequired: true, 98 wantResultMetric: stats.FileRequiredResultOK, 99 }, 100 { 101 name: "file required if file size == max file size", 102 path: "boot/foo/voo/zoo/vmlinuz", 103 fileSizeBytes: 1000 * units.KiB, 104 maxFileSizeBytes: 1000 * units.KiB, 105 wantRequired: true, 106 wantResultMetric: stats.FileRequiredResultOK, 107 }, 108 { 109 name: "file not required if file size > max file size", 110 path: "boot/foo/voo/zoo/vmlinuz", 111 fileSizeBytes: 1000 * units.KiB, 112 maxFileSizeBytes: 100 * units.KiB, 113 wantRequired: false, 114 wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, 115 }, 116 { 117 name: "file required if max file size set to 0", 118 path: "boot/foo/voo/zoo/vmlinuz", 119 fileSizeBytes: 100 * units.KiB, 120 maxFileSizeBytes: 0, 121 wantRequired: true, 122 wantResultMetric: stats.FileRequiredResultOK, 123 }, 124 { 125 name: "not required", 126 path: "usr/lib/foo/vmlinuzfoo", 127 wantRequired: false, 128 }, 129 { 130 name: "not required", 131 path: "boot/foo/voo/zoo/foovmlinuz-", 132 wantRequired: false, 133 }, 134 { 135 name: "not required", 136 path: "boot/foo/voo/zoo/vmlinuz.old", 137 wantRequired: false, 138 }, 139 { 140 name: "not required", 141 path: "usr/foo/voo/zoo/vmlinuz.old", 142 wantRequired: false, 143 }, 144 { 145 name: "not required", 146 path: "var/foo/voo/zoo/vmlinuz-x.y.z", 147 wantRequired: false, 148 }, 149 } 150 151 for _, tt := range tests { 152 t.Run(tt.name, func(t *testing.T) { 153 collector := testcollector.New() 154 var e filesystem.Extractor = vmlinuz.New(vmlinuz.Config{ 155 Stats: collector, 156 MaxFileSizeBytes: tt.maxFileSizeBytes, 157 }) 158 159 fileSizeBytes := tt.fileSizeBytes 160 if fileSizeBytes == 0 { 161 fileSizeBytes = 1000 162 } 163 164 isRequired := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{ 165 FileName: filepath.Base(tt.path), 166 FileMode: fs.ModePerm, 167 FileSize: fileSizeBytes, 168 })) 169 if isRequired != tt.wantRequired { 170 t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, isRequired, tt.wantRequired) 171 } 172 173 gotResultMetric := collector.FileRequiredResult(tt.path) 174 if tt.wantResultMetric != "" && gotResultMetric != tt.wantResultMetric { 175 t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) 176 } 177 }) 178 } 179 } 180 181 const UbuntuJammy = `PRETTY_NAME="Ubuntu 22.04.5 LTS" 182 NAME="Ubuntu" 183 VERSION_ID="22.04" 184 VERSION="22.04.5 LTS (Jammy Jellyfish)" 185 VERSION_CODENAME=jammy 186 ID=ubuntu 187 ID_LIKE=debian 188 HOME_URL="https://www.ubuntu.com/" 189 SUPPORT_URL="https://help.ubuntu.com/" 190 BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" 191 PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" 192 UBUNTU_CODENAME=jammy 193 ` 194 195 func TestExtract(t *testing.T) { 196 tests := []struct { 197 name string 198 path string 199 osrelease string 200 cfg vmlinuz.Config 201 wantPackages []*extractor.Package 202 wantErr error 203 wantResultMetric stats.FileExtractedResult 204 }{ 205 { 206 name: "valid vmlinuz file", 207 path: "testdata/valid", 208 osrelease: UbuntuJammy, 209 wantPackages: []*extractor.Package{ 210 { 211 Name: "Linux Kernel", 212 Version: "6.8.0-49-generic", 213 Metadata: &vmlinuzmeta.Metadata{ 214 Name: "Linux Kernel", 215 Version: "6.8.0-49-generic", 216 Architecture: "x86", 217 ExtendedVersion: "6.8.0-49-generic (buildd@lcy02-amd64-103) #49~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Nov 6 17:42:15 UTC 2", 218 Format: "bzImage", 219 SwapDevice: 14, 220 VideoMode: "Video mode 65535", 221 OSID: "ubuntu", 222 OSVersionCodename: "jammy", 223 OSVersionID: "22.04", 224 }, 225 Locations: []string{"testdata/valid"}, 226 }, 227 }, 228 wantResultMetric: stats.FileExtractedResultSuccess, 229 }, 230 { 231 name: "invalid vmlinuz file", 232 path: "testdata/invalid", 233 osrelease: UbuntuJammy, 234 wantPackages: nil, 235 }, 236 } 237 238 for _, tt := range tests { 239 t.Run(tt.name, func(t *testing.T) { 240 collector := testcollector.New() 241 var e filesystem.Extractor = vmlinuz.New(vmlinuz.Config{ 242 Stats: collector, 243 MaxFileSizeBytes: 100, 244 }) 245 246 d := t.TempDir() 247 createOsRelease(t, d, tt.osrelease) 248 249 // Opening and Reading the Test File 250 r, err := os.Open(tt.path) 251 defer func() { 252 if err = r.Close(); err != nil { 253 t.Errorf("Close(): %v", err) 254 } 255 }() 256 if err != nil { 257 t.Fatal(err) 258 } 259 260 info, err := os.Stat(tt.path) 261 if err != nil { 262 t.Fatalf("Failed to stat test file: %v", err) 263 } 264 265 input := &filesystem.ScanInput{ 266 FS: scalibrfs.DirFS(d), Path: tt.path, Reader: r, Root: d, Info: info, 267 } 268 269 got, err := e.Extract(t.Context(), input) 270 271 wantInv := inventory.Inventory{Packages: tt.wantPackages} 272 if diff := cmp.Diff(wantInv, got); diff != "" { 273 t.Errorf("Package mismatch (-want +got):\n%s", diff) 274 } 275 }) 276 } 277 } 278 279 func createOsRelease(t *testing.T, root string, content string) { 280 t.Helper() 281 _ = os.MkdirAll(filepath.Join(root, "etc"), 0755) 282 err := os.WriteFile(filepath.Join(root, "etc/os-release"), []byte(content), 0644) 283 if err != nil { 284 t.Fatalf("write to %s: %v\n", filepath.Join(root, "etc/os-release"), err) 285 } 286 }