github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/language/ruby/gemfilelock/gemfilelock_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 gemfilelock_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/language/ruby/gemfilelock"
    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 TestExtractor_FileRequired(t *testing.T) {
    31  	tests := []struct {
    32  		inputPath string
    33  		want      bool
    34  	}{
    35  		{
    36  			inputPath: "",
    37  			want:      false,
    38  		},
    39  		{
    40  			inputPath: "Gemfile.lock",
    41  			want:      true,
    42  		},
    43  		{
    44  			inputPath: "gems.locked",
    45  			want:      true,
    46  		},
    47  		{
    48  			inputPath: "path/to/my/Gemfile.lock",
    49  			want:      true,
    50  		},
    51  		{
    52  			inputPath: "path/to/my/gems.locked",
    53  			want:      true,
    54  		},
    55  		{
    56  			inputPath: "path/to/my/Gemfile.lock/file",
    57  			want:      false,
    58  		},
    59  		{
    60  			inputPath: "path/to/my/Gemfile.lock.file",
    61  			want:      false,
    62  		},
    63  		{
    64  			inputPath: "path.to.my.Gemfile.lock",
    65  			want:      false,
    66  		},
    67  	}
    68  	for _, tt := range tests {
    69  		t.Run(tt.inputPath, func(t *testing.T) {
    70  			e := gemfilelock.Extractor{}
    71  			got := e.FileRequired(simplefileapi.New(tt.inputPath, nil))
    72  			if got != tt.want {
    73  				t.Errorf("FileRequired(%s, FileInfo) got = %v, want %v", tt.inputPath, got, tt.want)
    74  			}
    75  		})
    76  	}
    77  }
    78  
    79  func TestExtractor_Extract(t *testing.T) {
    80  	tests := []extracttest.TestTableEntry{
    81  		{
    82  			Name: "no spec section",
    83  			InputConfig: extracttest.ScanInputMockConfig{
    84  				Path: "testdata/no-spec-section.lock",
    85  			},
    86  			WantPackages: []*extractor.Package{},
    87  		},
    88  		{
    89  			Name: "no gem section",
    90  			InputConfig: extracttest.ScanInputMockConfig{
    91  				Path: "testdata/no-gem-section.lock",
    92  			},
    93  			WantPackages: []*extractor.Package{},
    94  		},
    95  		{
    96  			Name: "no gems",
    97  			InputConfig: extracttest.ScanInputMockConfig{
    98  				Path: "testdata/no-gems.lock",
    99  			},
   100  			WantPackages: []*extractor.Package{},
   101  		},
   102  		{
   103  			Name: "invalid spec",
   104  			InputConfig: extracttest.ScanInputMockConfig{
   105  				Path: "testdata/invalid.lock",
   106  			},
   107  			WantPackages: []*extractor.Package{},
   108  		},
   109  		{
   110  			Name: "one gem",
   111  			InputConfig: extracttest.ScanInputMockConfig{
   112  				Path: "testdata/one-gem.lock",
   113  			},
   114  			WantPackages: []*extractor.Package{
   115  				{
   116  					Name:      "ast",
   117  					Version:   "2.4.2",
   118  					PURLType:  purl.TypeGem,
   119  					Locations: []string{"testdata/one-gem.lock"},
   120  				},
   121  			},
   122  		},
   123  		{
   124  			Name: "trailing source section ",
   125  			InputConfig: extracttest.ScanInputMockConfig{
   126  				Path: "testdata/source-section-at-end.lock",
   127  			},
   128  			WantPackages: []*extractor.Package{
   129  				{
   130  					Name:      "ast",
   131  					Version:   "2.4.2",
   132  					PURLType:  purl.TypeGem,
   133  					Locations: []string{"testdata/source-section-at-end.lock"},
   134  				},
   135  			},
   136  		},
   137  		{
   138  			Name: "some gems",
   139  			InputConfig: extracttest.ScanInputMockConfig{
   140  				Path: "testdata/some-gems.lock",
   141  			},
   142  			WantPackages: []*extractor.Package{
   143  				{
   144  					Name:      "coderay",
   145  					Version:   "1.1.3",
   146  					PURLType:  purl.TypeGem,
   147  					Locations: []string{"testdata/some-gems.lock"},
   148  				},
   149  				{
   150  					Name:      "method_source",
   151  					Version:   "1.0.0",
   152  					PURLType:  purl.TypeGem,
   153  					Locations: []string{"testdata/some-gems.lock"},
   154  				},
   155  				{
   156  					Name:      "pry",
   157  					Version:   "0.14.1",
   158  					PURLType:  purl.TypeGem,
   159  					Locations: []string{"testdata/some-gems.lock"},
   160  				},
   161  			},
   162  		},
   163  		{
   164  			Name: "multiple gems",
   165  			InputConfig: extracttest.ScanInputMockConfig{
   166  				Path: "testdata/multiple-gems.lock",
   167  			},
   168  			WantPackages: []*extractor.Package{
   169  				{
   170  					Name:      "bundler-audit",
   171  					Version:   "0.9.0.1",
   172  					PURLType:  purl.TypeGem,
   173  					Locations: []string{"testdata/multiple-gems.lock"},
   174  				},
   175  				{
   176  					Name:      "coderay",
   177  					Version:   "1.1.3",
   178  					PURLType:  purl.TypeGem,
   179  					Locations: []string{"testdata/multiple-gems.lock"},
   180  				},
   181  				{
   182  					Name:      "dotenv",
   183  					Version:   "2.7.6",
   184  					PURLType:  purl.TypeGem,
   185  					Locations: []string{"testdata/multiple-gems.lock"},
   186  				},
   187  				{
   188  					Name:      "method_source",
   189  					Version:   "1.0.0",
   190  					PURLType:  purl.TypeGem,
   191  					Locations: []string{"testdata/multiple-gems.lock"},
   192  				},
   193  				{
   194  					Name:      "pry",
   195  					Version:   "0.14.1",
   196  					PURLType:  purl.TypeGem,
   197  					Locations: []string{"testdata/multiple-gems.lock"},
   198  				},
   199  				{
   200  					Name:      "thor",
   201  					Version:   "1.2.1",
   202  					PURLType:  purl.TypeGem,
   203  					Locations: []string{"testdata/multiple-gems.lock"},
   204  				},
   205  			},
   206  		},
   207  		{
   208  			Name: "rails",
   209  			InputConfig: extracttest.ScanInputMockConfig{
   210  				Path: "testdata/rails.lock",
   211  			},
   212  			WantPackages: []*extractor.Package{
   213  				{
   214  					Name:      "actioncable",
   215  					Version:   "7.0.2.2",
   216  					PURLType:  purl.TypeGem,
   217  					Locations: []string{"testdata/rails.lock"},
   218  				},
   219  				{
   220  					Name:      "actionmailbox",
   221  					Version:   "7.0.2.2",
   222  					PURLType:  purl.TypeGem,
   223  					Locations: []string{"testdata/rails.lock"},
   224  				},
   225  				{
   226  					Name:      "actionmailer",
   227  					Version:   "7.0.2.2",
   228  					PURLType:  purl.TypeGem,
   229  					Locations: []string{"testdata/rails.lock"},
   230  				},
   231  				{
   232  					Name:      "actionpack",
   233  					Version:   "7.0.2.2",
   234  					PURLType:  purl.TypeGem,
   235  					Locations: []string{"testdata/rails.lock"},
   236  				},
   237  				{
   238  					Name:      "actiontext",
   239  					Version:   "7.0.2.2",
   240  					PURLType:  purl.TypeGem,
   241  					Locations: []string{"testdata/rails.lock"},
   242  				},
   243  				{
   244  					Name:      "actionview",
   245  					Version:   "7.0.2.2",
   246  					PURLType:  purl.TypeGem,
   247  					Locations: []string{"testdata/rails.lock"},
   248  				},
   249  				{
   250  					Name:      "activejob",
   251  					Version:   "7.0.2.2",
   252  					PURLType:  purl.TypeGem,
   253  					Locations: []string{"testdata/rails.lock"},
   254  				},
   255  				{
   256  					Name:      "activemodel",
   257  					Version:   "7.0.2.2",
   258  					PURLType:  purl.TypeGem,
   259  					Locations: []string{"testdata/rails.lock"},
   260  				},
   261  				{
   262  					Name:      "activerecord",
   263  					Version:   "7.0.2.2",
   264  					PURLType:  purl.TypeGem,
   265  					Locations: []string{"testdata/rails.lock"},
   266  				},
   267  				{
   268  					Name:      "activestorage",
   269  					Version:   "7.0.2.2",
   270  					PURLType:  purl.TypeGem,
   271  					Locations: []string{"testdata/rails.lock"},
   272  				},
   273  				{
   274  					Name:      "activesupport",
   275  					Version:   "7.0.2.2",
   276  					PURLType:  purl.TypeGem,
   277  					Locations: []string{"testdata/rails.lock"},
   278  				},
   279  				{
   280  					Name:      "builder",
   281  					Version:   "3.2.4",
   282  					PURLType:  purl.TypeGem,
   283  					Locations: []string{"testdata/rails.lock"},
   284  				},
   285  				{
   286  					Name:      "concurrent-ruby",
   287  					Version:   "1.1.9",
   288  					PURLType:  purl.TypeGem,
   289  					Locations: []string{"testdata/rails.lock"},
   290  				},
   291  				{
   292  					Name:      "crass",
   293  					Version:   "1.0.6",
   294  					PURLType:  purl.TypeGem,
   295  					Locations: []string{"testdata/rails.lock"},
   296  				},
   297  				{
   298  					Name:      "digest",
   299  					Version:   "3.1.0",
   300  					PURLType:  purl.TypeGem,
   301  					Locations: []string{"testdata/rails.lock"},
   302  				},
   303  				{
   304  					Name:      "erubi",
   305  					Version:   "1.10.0",
   306  					PURLType:  purl.TypeGem,
   307  					Locations: []string{"testdata/rails.lock"},
   308  				},
   309  				{
   310  					Name:      "globalid",
   311  					Version:   "1.0.0",
   312  					PURLType:  purl.TypeGem,
   313  					Locations: []string{"testdata/rails.lock"},
   314  				},
   315  				{
   316  					Name:      "i18n",
   317  					Version:   "1.10.0",
   318  					PURLType:  purl.TypeGem,
   319  					Locations: []string{"testdata/rails.lock"},
   320  				},
   321  				{
   322  					Name:      "io-wait",
   323  					Version:   "0.2.1",
   324  					PURLType:  purl.TypeGem,
   325  					Locations: []string{"testdata/rails.lock"},
   326  				},
   327  				{
   328  					Name:      "loofah",
   329  					Version:   "2.14.0",
   330  					PURLType:  purl.TypeGem,
   331  					Locations: []string{"testdata/rails.lock"},
   332  				},
   333  				{
   334  					Name:      "mail",
   335  					Version:   "2.7.1",
   336  					PURLType:  purl.TypeGem,
   337  					Locations: []string{"testdata/rails.lock"},
   338  				},
   339  				{
   340  					Name:      "marcel",
   341  					Version:   "1.0.2",
   342  					PURLType:  purl.TypeGem,
   343  					Locations: []string{"testdata/rails.lock"},
   344  				},
   345  				{
   346  					Name:      "method_source",
   347  					Version:   "1.0.0",
   348  					PURLType:  purl.TypeGem,
   349  					Locations: []string{"testdata/rails.lock"},
   350  				},
   351  				{
   352  					Name:      "mini_mime",
   353  					Version:   "1.1.2",
   354  					PURLType:  purl.TypeGem,
   355  					Locations: []string{"testdata/rails.lock"},
   356  				},
   357  				{
   358  					Name:      "minitest",
   359  					Version:   "5.15.0",
   360  					PURLType:  purl.TypeGem,
   361  					Locations: []string{"testdata/rails.lock"},
   362  				},
   363  				{
   364  					Name:      "net-imap",
   365  					Version:   "0.2.3",
   366  					PURLType:  purl.TypeGem,
   367  					Locations: []string{"testdata/rails.lock"},
   368  				},
   369  				{
   370  					Name:      "net-pop",
   371  					Version:   "0.1.1",
   372  					PURLType:  purl.TypeGem,
   373  					Locations: []string{"testdata/rails.lock"},
   374  				},
   375  				{
   376  					Name:      "net-protocol",
   377  					Version:   "0.1.2",
   378  					PURLType:  purl.TypeGem,
   379  					Locations: []string{"testdata/rails.lock"},
   380  				},
   381  				{
   382  					Name:      "net-smtp",
   383  					Version:   "0.3.1",
   384  					PURLType:  purl.TypeGem,
   385  					Locations: []string{"testdata/rails.lock"},
   386  				},
   387  				{
   388  					Name:      "nio4r",
   389  					Version:   "2.5.8",
   390  					PURLType:  purl.TypeGem,
   391  					Locations: []string{"testdata/rails.lock"},
   392  				},
   393  				{
   394  					Name:      "racc",
   395  					Version:   "1.6.0",
   396  					PURLType:  purl.TypeGem,
   397  					Locations: []string{"testdata/rails.lock"},
   398  				},
   399  				{
   400  					Name:      "rack",
   401  					Version:   "2.2.3",
   402  					PURLType:  purl.TypeGem,
   403  					Locations: []string{"testdata/rails.lock"},
   404  				},
   405  				{
   406  					Name:      "rack-test",
   407  					Version:   "1.1.0",
   408  					PURLType:  purl.TypeGem,
   409  					Locations: []string{"testdata/rails.lock"},
   410  				},
   411  				{
   412  					Name:      "rails",
   413  					Version:   "7.0.2.2",
   414  					PURLType:  purl.TypeGem,
   415  					Locations: []string{"testdata/rails.lock"},
   416  				},
   417  				{
   418  					Name:      "rails-dom-testing",
   419  					Version:   "2.0.3",
   420  					PURLType:  purl.TypeGem,
   421  					Locations: []string{"testdata/rails.lock"},
   422  				},
   423  				{
   424  					Name:      "rails-html-sanitizer",
   425  					Version:   "1.4.2",
   426  					PURLType:  purl.TypeGem,
   427  					Locations: []string{"testdata/rails.lock"},
   428  				},
   429  				{
   430  					Name:      "railties",
   431  					Version:   "7.0.2.2",
   432  					PURLType:  purl.TypeGem,
   433  					Locations: []string{"testdata/rails.lock"},
   434  				},
   435  				{
   436  					Name:      "rake",
   437  					Version:   "13.0.6",
   438  					PURLType:  purl.TypeGem,
   439  					Locations: []string{"testdata/rails.lock"},
   440  				},
   441  				{
   442  					Name:      "strscan",
   443  					Version:   "3.0.1",
   444  					PURLType:  purl.TypeGem,
   445  					Locations: []string{"testdata/rails.lock"},
   446  				},
   447  				{
   448  					Name:      "thor",
   449  					Version:   "1.2.1",
   450  					PURLType:  purl.TypeGem,
   451  					Locations: []string{"testdata/rails.lock"},
   452  				},
   453  				{
   454  					Name:      "timeout",
   455  					Version:   "0.2.0",
   456  					PURLType:  purl.TypeGem,
   457  					Locations: []string{"testdata/rails.lock"},
   458  				},
   459  				{
   460  					Name:      "tzinfo",
   461  					Version:   "2.0.4",
   462  					PURLType:  purl.TypeGem,
   463  					Locations: []string{"testdata/rails.lock"},
   464  				},
   465  				{
   466  					Name:      "websocket-driver",
   467  					Version:   "0.7.5",
   468  					PURLType:  purl.TypeGem,
   469  					Locations: []string{"testdata/rails.lock"},
   470  				},
   471  				{
   472  					Name:      "websocket-extensions",
   473  					Version:   "0.1.5",
   474  					PURLType:  purl.TypeGem,
   475  					Locations: []string{"testdata/rails.lock"},
   476  				},
   477  				{
   478  					Name:      "zeitwerk",
   479  					Version:   "2.5.4",
   480  					PURLType:  purl.TypeGem,
   481  					Locations: []string{"testdata/rails.lock"},
   482  				},
   483  				{
   484  					Name:      "nokogiri",
   485  					Version:   "1.13.3",
   486  					PURLType:  purl.TypeGem,
   487  					Locations: []string{"testdata/rails.lock"},
   488  				},
   489  			},
   490  		},
   491  		{
   492  			Name: "rubocop",
   493  			InputConfig: extracttest.ScanInputMockConfig{
   494  				Path: "testdata/rubocop.lock",
   495  			},
   496  			WantPackages: []*extractor.Package{
   497  				{
   498  					Name:      "ast",
   499  					Version:   "2.4.2",
   500  					PURLType:  purl.TypeGem,
   501  					Locations: []string{"testdata/rubocop.lock"},
   502  				},
   503  				{
   504  					Name:      "parallel",
   505  					Version:   "1.21.0",
   506  					PURLType:  purl.TypeGem,
   507  					Locations: []string{"testdata/rubocop.lock"},
   508  				},
   509  				{
   510  					Name:      "parser",
   511  					Version:   "3.1.1.0",
   512  					PURLType:  purl.TypeGem,
   513  					Locations: []string{"testdata/rubocop.lock"},
   514  				},
   515  				{
   516  					Name:      "rainbow",
   517  					Version:   "3.1.1",
   518  					PURLType:  purl.TypeGem,
   519  					Locations: []string{"testdata/rubocop.lock"},
   520  				},
   521  				{
   522  					Name:      "regexp_parser",
   523  					Version:   "2.2.1",
   524  					PURLType:  purl.TypeGem,
   525  					Locations: []string{"testdata/rubocop.lock"},
   526  				},
   527  				{
   528  					Name:      "rexml",
   529  					Version:   "3.2.5",
   530  					PURLType:  purl.TypeGem,
   531  					Locations: []string{"testdata/rubocop.lock"},
   532  				},
   533  				{
   534  					Name:      "rubocop",
   535  					Version:   "1.25.1",
   536  					PURLType:  purl.TypeGem,
   537  					Locations: []string{"testdata/rubocop.lock"},
   538  				},
   539  				{
   540  					Name:      "rubocop-ast",
   541  					Version:   "1.16.0",
   542  					PURLType:  purl.TypeGem,
   543  					Locations: []string{"testdata/rubocop.lock"},
   544  				},
   545  				{
   546  					Name:      "ruby-progressbar",
   547  					Version:   "1.11.0",
   548  					PURLType:  purl.TypeGem,
   549  					Locations: []string{"testdata/rubocop.lock"},
   550  				},
   551  				{
   552  					Name:      "unicode-display_width",
   553  					Version:   "2.1.0",
   554  					PURLType:  purl.TypeGem,
   555  					Locations: []string{"testdata/rubocop.lock"},
   556  				},
   557  			},
   558  		},
   559  		{
   560  			Name: "has local gem",
   561  			InputConfig: extracttest.ScanInputMockConfig{
   562  				Path: "testdata/has-local-gem.lock",
   563  			},
   564  			WantPackages: []*extractor.Package{
   565  				{
   566  					Name:      "backbone-on-rails",
   567  					Version:   "1.2.0.0",
   568  					PURLType:  purl.TypeGem,
   569  					Locations: []string{"testdata/has-local-gem.lock"},
   570  				},
   571  				{
   572  					Name:      "actionpack",
   573  					Version:   "7.0.2.2",
   574  					PURLType:  purl.TypeGem,
   575  					Locations: []string{"testdata/has-local-gem.lock"},
   576  				},
   577  				{
   578  					Name:      "actionview",
   579  					Version:   "7.0.2.2",
   580  					PURLType:  purl.TypeGem,
   581  					Locations: []string{"testdata/has-local-gem.lock"},
   582  				},
   583  				{
   584  					Name:      "activesupport",
   585  					Version:   "7.0.2.2",
   586  					PURLType:  purl.TypeGem,
   587  					Locations: []string{"testdata/has-local-gem.lock"},
   588  				},
   589  				{
   590  					Name:      "builder",
   591  					Version:   "3.2.4",
   592  					PURLType:  purl.TypeGem,
   593  					Locations: []string{"testdata/has-local-gem.lock"},
   594  				},
   595  				{
   596  					Name:      "coffee-script",
   597  					Version:   "2.4.1",
   598  					PURLType:  purl.TypeGem,
   599  					Locations: []string{"testdata/has-local-gem.lock"},
   600  				},
   601  				{
   602  					Name:      "coffee-script-source",
   603  					Version:   "1.12.2",
   604  					PURLType:  purl.TypeGem,
   605  					Locations: []string{"testdata/has-local-gem.lock"},
   606  				},
   607  				{
   608  					Name:      "concurrent-ruby",
   609  					Version:   "1.1.9",
   610  					PURLType:  purl.TypeGem,
   611  					Locations: []string{"testdata/has-local-gem.lock"},
   612  				},
   613  				{
   614  					Name:      "crass",
   615  					Version:   "1.0.6",
   616  					PURLType:  purl.TypeGem,
   617  					Locations: []string{"testdata/has-local-gem.lock"},
   618  				},
   619  				{
   620  					Name:      "eco",
   621  					Version:   "1.0.0",
   622  					PURLType:  purl.TypeGem,
   623  					Locations: []string{"testdata/has-local-gem.lock"},
   624  				},
   625  				{
   626  					Name:      "ejs",
   627  					Version:   "1.1.1",
   628  					PURLType:  purl.TypeGem,
   629  					Locations: []string{"testdata/has-local-gem.lock"},
   630  				},
   631  				{
   632  					Name:      "erubi",
   633  					Version:   "1.10.0",
   634  					PURLType:  purl.TypeGem,
   635  					Locations: []string{"testdata/has-local-gem.lock"},
   636  				},
   637  				{
   638  					Name:      "execjs",
   639  					Version:   "2.8.1",
   640  					PURLType:  purl.TypeGem,
   641  					Locations: []string{"testdata/has-local-gem.lock"},
   642  				},
   643  				{
   644  					Name:      "i18n",
   645  					Version:   "1.10.0",
   646  					PURLType:  purl.TypeGem,
   647  					Locations: []string{"testdata/has-local-gem.lock"},
   648  				},
   649  				{
   650  					Name:      "jquery-rails",
   651  					Version:   "4.4.0",
   652  					PURLType:  purl.TypeGem,
   653  					Locations: []string{"testdata/has-local-gem.lock"},
   654  				},
   655  				{
   656  					Name:      "loofah",
   657  					Version:   "2.14.0",
   658  					PURLType:  purl.TypeGem,
   659  					Locations: []string{"testdata/has-local-gem.lock"},
   660  				},
   661  				{
   662  					Name:      "method_source",
   663  					Version:   "1.0.0",
   664  					PURLType:  purl.TypeGem,
   665  					Locations: []string{"testdata/has-local-gem.lock"},
   666  				},
   667  				{
   668  					Name:      "minitest",
   669  					Version:   "5.15.0",
   670  					PURLType:  purl.TypeGem,
   671  					Locations: []string{"testdata/has-local-gem.lock"},
   672  				},
   673  				{
   674  					Name:      "racc",
   675  					Version:   "1.6.0",
   676  					PURLType:  purl.TypeGem,
   677  					Locations: []string{"testdata/has-local-gem.lock"},
   678  				},
   679  				{
   680  					Name:      "rack",
   681  					Version:   "2.2.3",
   682  					PURLType:  purl.TypeGem,
   683  					Locations: []string{"testdata/has-local-gem.lock"},
   684  				},
   685  				{
   686  					Name:      "rack-test",
   687  					Version:   "1.1.0",
   688  					PURLType:  purl.TypeGem,
   689  					Locations: []string{"testdata/has-local-gem.lock"},
   690  				},
   691  				{
   692  					Name:      "rails-dom-testing",
   693  					Version:   "2.0.3",
   694  					PURLType:  purl.TypeGem,
   695  					Locations: []string{"testdata/has-local-gem.lock"},
   696  				},
   697  				{
   698  					Name:      "rails-html-sanitizer",
   699  					Version:   "1.4.2",
   700  					PURLType:  purl.TypeGem,
   701  					Locations: []string{"testdata/has-local-gem.lock"},
   702  				},
   703  				{
   704  					Name:      "railties",
   705  					Version:   "7.0.2.2",
   706  					PURLType:  purl.TypeGem,
   707  					Locations: []string{"testdata/has-local-gem.lock"},
   708  				},
   709  				{
   710  					Name:      "rake",
   711  					Version:   "13.0.6",
   712  					PURLType:  purl.TypeGem,
   713  					Locations: []string{"testdata/has-local-gem.lock"},
   714  				},
   715  				{
   716  					Name:      "thor",
   717  					Version:   "1.2.1",
   718  					PURLType:  purl.TypeGem,
   719  					Locations: []string{"testdata/has-local-gem.lock"},
   720  				},
   721  				{
   722  					Name:      "tzinfo",
   723  					Version:   "2.0.4",
   724  					PURLType:  purl.TypeGem,
   725  					Locations: []string{"testdata/has-local-gem.lock"},
   726  				},
   727  				{
   728  					Name:      "zeitwerk",
   729  					Version:   "2.5.4",
   730  					PURLType:  purl.TypeGem,
   731  					Locations: []string{"testdata/has-local-gem.lock"},
   732  				},
   733  				{
   734  					Name:      "nokogiri",
   735  					Version:   "1.13.3",
   736  					PURLType:  purl.TypeGem,
   737  					Locations: []string{"testdata/has-local-gem.lock"},
   738  				},
   739  				{
   740  					Name:      "eco-source",
   741  					Version:   "1.1.0.rc.1",
   742  					PURLType:  purl.TypeGem,
   743  					Locations: []string{"testdata/has-local-gem.lock"},
   744  				},
   745  			},
   746  		},
   747  		{
   748  			Name: "has git gem",
   749  			InputConfig: extracttest.ScanInputMockConfig{
   750  				Path: "testdata/has-git-gem.lock",
   751  			},
   752  			WantPackages: []*extractor.Package{
   753  				{
   754  					Name:      "hanami-controller",
   755  					Version:   "2.0.0.alpha1",
   756  					PURLType:  purl.TypeGem,
   757  					Locations: []string{"testdata/has-git-gem.lock"},
   758  					SourceCode: &extractor.SourceCodeIdentifier{
   759  						Commit: "027dbe2e56397b534e859fc283990cad1b6addd6",
   760  					},
   761  				},
   762  				{
   763  					Name:      "hanami-utils",
   764  					Version:   "2.0.0.alpha1",
   765  					PURLType:  purl.TypeGem,
   766  					Locations: []string{"testdata/has-git-gem.lock"},
   767  					SourceCode: &extractor.SourceCodeIdentifier{
   768  						Commit: "5904fc9a70683b8749aa2861257d0c8c01eae4aa",
   769  					},
   770  				},
   771  				{
   772  					Name:      "concurrent-ruby",
   773  					Version:   "1.1.7",
   774  					PURLType:  purl.TypeGem,
   775  					Locations: []string{"testdata/has-git-gem.lock"},
   776  				},
   777  				{
   778  					Name:      "rack",
   779  					Version:   "2.2.3",
   780  					PURLType:  purl.TypeGem,
   781  					Locations: []string{"testdata/has-git-gem.lock"},
   782  				},
   783  				{
   784  					Name:      "transproc",
   785  					Version:   "1.1.1",
   786  					PURLType:  purl.TypeGem,
   787  					Locations: []string{"testdata/has-git-gem.lock"},
   788  				},
   789  			},
   790  		},
   791  	}
   792  
   793  	for _, tt := range tests {
   794  		t.Run(tt.Name, func(t *testing.T) {
   795  			extr := gemfilelock.Extractor{}
   796  
   797  			scanInput := extracttest.GenerateScanInputMock(t, tt.InputConfig)
   798  			defer extracttest.CloseTestScanInput(t, scanInput)
   799  
   800  			got, err := extr.Extract(t.Context(), &scanInput)
   801  
   802  			if diff := cmp.Diff(tt.WantErr, err, cmpopts.EquateErrors()); diff != "" {
   803  				t.Errorf("%s.Extract(%q) error diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   804  				return
   805  			}
   806  
   807  			wantInv := inventory.Inventory{Packages: tt.WantPackages}
   808  			if diff := cmp.Diff(wantInv, got, cmpopts.SortSlices(extracttest.PackageCmpLess)); diff != "" {
   809  				t.Errorf("%s.Extract(%q) diff (-want +got):\n%s", extr.Name(), tt.InputConfig.Path, diff)
   810  			}
   811  		})
   812  	}
   813  }