github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/dotnet/dotnetpe/dotnetpe_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 dotnetpe_test 16 17 import ( 18 "io/fs" 19 "path/filepath" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 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/language/dotnet/dotnetpe" 28 "github.com/google/osv-scalibr/extractor/filesystem/simplefileapi" 29 "github.com/google/osv-scalibr/purl" 30 "github.com/google/osv-scalibr/stats" 31 "github.com/google/osv-scalibr/testing/extracttest" 32 "github.com/google/osv-scalibr/testing/fakefs" 33 "github.com/google/osv-scalibr/testing/testcollector" 34 ) 35 36 func TestFileRequired(t *testing.T) { 37 tests := []struct { 38 name string 39 path string 40 // fileSizeBytes is set to 1K if not specified because the minimum size of a PE file is 41 // pe.TinyPESize // 97 42 fileSizeBytes int64 43 maxFileSizeBytes int64 44 wantRequired bool 45 wantResultMetric stats.FileRequiredResult 46 }{ 47 { 48 name: "executable file", 49 path: "test.exe", 50 wantRequired: true, 51 wantResultMetric: stats.FileRequiredResultOK, 52 }, 53 { 54 name: "executable file with upper case", 55 path: "test.Exe", 56 wantRequired: true, 57 wantResultMetric: stats.FileRequiredResultOK, 58 }, 59 { 60 name: ".dll", 61 path: "test.dll", 62 wantRequired: true, 63 wantResultMetric: stats.FileRequiredResultOK, 64 }, 65 { 66 name: "upper case .dll", 67 path: "test.DLL", 68 wantRequired: true, 69 wantResultMetric: stats.FileRequiredResultOK, 70 }, 71 { 72 name: "file without extension", 73 path: "test", 74 wantRequired: true, 75 wantResultMetric: stats.FileRequiredResultOK, 76 }, 77 { 78 name: "relative path", 79 path: "path/to/my/test.exe", 80 wantRequired: true, 81 wantResultMetric: stats.FileRequiredResultOK, 82 }, 83 { 84 name: "windows full path", 85 path: `C:\\path\\to\\my\\test.exe`, 86 wantRequired: true, 87 wantResultMetric: stats.FileRequiredResultOK, 88 }, 89 { 90 name: "file not required", 91 path: "/test.deps", 92 wantRequired: false, 93 }, 94 { 95 name: "file required if file size < max file size", 96 path: "test.exe", 97 fileSizeBytes: 100 * units.KiB, 98 maxFileSizeBytes: 1000 * units.KiB, 99 wantRequired: true, 100 wantResultMetric: stats.FileRequiredResultOK, 101 }, 102 { 103 name: "file not required if file size > max file size", 104 path: "test.exe", 105 fileSizeBytes: 1000 * units.KiB, 106 maxFileSizeBytes: 100 * units.KiB, 107 wantRequired: false, 108 wantResultMetric: stats.FileRequiredResultSizeLimitExceeded, 109 }, 110 } 111 112 for _, tt := range tests { 113 t.Run(tt.name, func(t *testing.T) { 114 cfg := dotnetpe.DefaultConfig() 115 collector := testcollector.New() 116 cfg.Stats = collector 117 if tt.maxFileSizeBytes != 0 { 118 cfg.MaxFileSizeBytes = tt.maxFileSizeBytes 119 } 120 var e filesystem.Extractor = dotnetpe.New(cfg) 121 122 fileSizeBytes := tt.fileSizeBytes 123 if fileSizeBytes == 0 { 124 fileSizeBytes = 1000 125 } 126 127 isRequired := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{ 128 FileName: filepath.Base(tt.path), 129 FileMode: fs.ModePerm, 130 FileSize: fileSizeBytes, 131 })) 132 if isRequired != tt.wantRequired { 133 t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, isRequired, tt.wantRequired) 134 } 135 136 gotResultMetric := collector.FileRequiredResult(tt.path) 137 if tt.wantResultMetric != "" && gotResultMetric != tt.wantResultMetric { 138 t.Errorf("FileRequired(%s) recorded result metric %v, want result metric %v", tt.path, gotResultMetric, tt.wantResultMetric) 139 } 140 }) 141 } 142 } 143 144 func TestExtract(t *testing.T) { 145 tests := []extracttest.TestTableEntry{ 146 { 147 Name: "valid .dll", 148 InputConfig: extracttest.ScanInputMockConfig{ 149 Path: "testdata/HelloWorldApp.dll", 150 }, 151 WantPackages: []*extractor.Package{ 152 {Name: "Flurl.Http.dll", Version: "4.0.2.0", PURLType: purl.TypeNuget}, 153 {Name: "HelloWorldApp.dll", Version: "1.0.0.0", PURLType: purl.TypeNuget}, 154 {Name: "Newtonsoft.Json.dll", Version: "13.0.0.0", PURLType: purl.TypeNuget}, 155 {Name: "System.Collections.dll", Version: "9.0.0.0", PURLType: purl.TypeNuget}, 156 {Name: "System.Console.dll", Version: "9.0.0.0", PURLType: purl.TypeNuget}, 157 {Name: "System.Net.Http.dll", Version: "9.0.0.0", PURLType: purl.TypeNuget}, 158 {Name: "System.Runtime.dll", Version: "9.0.0.0", PURLType: purl.TypeNuget}, 159 }, 160 }, 161 { 162 Name: "valid .exe", 163 InputConfig: extracttest.ScanInputMockConfig{ 164 Path: "testdata/HelloWorldApp.exe", 165 }, 166 WantPackages: []*extractor.Package{ 167 {Name: "HelloWorldApp.dll", Version: "1.0.0.0", PURLType: purl.TypeNuget}, 168 }, 169 }, 170 { 171 Name: "Empty .dll", 172 InputConfig: extracttest.ScanInputMockConfig{ 173 Path: "testdata/Empty.dll", 174 }, 175 WantErr: extracttest.ContainsErrStr{Str: "the file header does not contain magic bytes"}, 176 }, 177 { 178 Name: "Invalid .dll", 179 InputConfig: extracttest.ScanInputMockConfig{ 180 Path: "testdata/Invalid.dll", 181 }, 182 WantErr: extracttest.ContainsErrStr{Str: "the file header does not contain magic bytes"}, 183 }, 184 } 185 186 for _, tt := range tests { 187 t.Run(tt.Name, func(t *testing.T) { 188 extr := dotnetpe.New(dotnetpe.DefaultConfig()) 189 190 input := extracttest.GenerateScanInputMock(t, tt.InputConfig) 191 defer extracttest.CloseTestScanInput(t, input) 192 193 got, err := extr.Extract(t.Context(), &input) 194 if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" { 195 t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 196 return 197 } 198 199 if diff := cmp.Diff(tt.WantPackages, got.Packages, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" { 200 t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff) 201 } 202 }) 203 } 204 }