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  }