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  }