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  }