github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/os/nix/nix_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 nix_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/os/nix"
    27  	nixmeta "github.com/google/osv-scalibr/extractor/filesystem/os/nix/metadata"
    28  	"github.com/google/osv-scalibr/extractor/filesystem/simplefileapi"
    29  	scalibrfs "github.com/google/osv-scalibr/fs"
    30  	"github.com/google/osv-scalibr/inventory"
    31  	"github.com/google/osv-scalibr/purl"
    32  	"github.com/google/osv-scalibr/stats"
    33  	"github.com/google/osv-scalibr/testing/fakefs"
    34  )
    35  
    36  func TestFileRequired(t *testing.T) {
    37  	tests := []struct {
    38  		name             string
    39  		path             string
    40  		fileSizeBytes    int64
    41  		maxFileSizeBytes int64
    42  		wantRequired     bool
    43  		wantResultMetric stats.FileRequiredResult
    44  	}{
    45  		{
    46  			name:             "nix/store directory",
    47  			path:             "nix/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager-0.28/xxx",
    48  			wantRequired:     true,
    49  			wantResultMetric: stats.FileRequiredResultOK,
    50  		},
    51  		{
    52  			name:         "invalid nix/store directory",
    53  			path:         "nix/xxx/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager-0.28/xxx",
    54  			wantRequired: false,
    55  		},
    56  		{
    57  			name:         "invalid nix/store directory",
    58  			path:         "nix/storefoo/sss",
    59  			wantRequired: false,
    60  		},
    61  		{
    62  			name:         "nix/store directory already parsed",
    63  			path:         "nix/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager-0.28/sss",
    64  			wantRequired: false,
    65  		},
    66  		{
    67  			name:         "no nix/store prefix",
    68  			path:         "foo/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager-0.28/sss",
    69  			wantRequired: false,
    70  		},
    71  	}
    72  
    73  	var e = nix.New()
    74  
    75  	for _, tt := range tests {
    76  		t.Run(tt.name, func(t *testing.T) {
    77  			isRequired := e.FileRequired(simplefileapi.New(tt.path, fakefs.FakeFileInfo{
    78  				FileName: filepath.Base(tt.path),
    79  				FileMode: fs.ModePerm,
    80  			}))
    81  
    82  			if isRequired != tt.wantRequired {
    83  				t.Fatalf("FileRequired(%s): got %v, want %v", tt.path, isRequired, tt.wantRequired)
    84  			}
    85  		})
    86  	}
    87  }
    88  
    89  func TestExtract(t *testing.T) {
    90  	const NixVicuna = `ANSI_COLOR="1;34"
    91  	BUG_REPORT_URL="https://github.com/NixOS/nixpackages/issues"
    92  	BUILD_ID="24.11.710315.b681065d0919"
    93  	CPE_NAME="cpe:/o:nixos:nixos:24.11"
    94  	DEFAULT_HOSTNAME=nixos
    95  	DOCUMENTATION_URL="https://nixos.org/learn.html"
    96  	HOME_URL="https://nixos.org/"
    97  	ID=nixos
    98  	ID_LIKE=""
    99  	IMAGE_ID=""
   100  	IMAGE_VERSION=""
   101  	LOGO="nix-snowflake"
   102  	NAME=NixOS
   103  	PRETTY_NAME="NixOS 24.11 (Vicuna)"
   104  	SUPPORT_END="2025-06-30"
   105  	SUPPORT_URL="https://nixos.org/community.html"
   106  	VARIANT=""
   107  	VARIANT_ID=""
   108  	VENDOR_NAME="NixOS"
   109  	VENDOR_URL="https://nixos.org/"
   110  	VERSION="24.11 (Vicuna)"
   111  	VERSION_CODENAME=vicuna
   112  	VERSION_ID="24.11"`
   113  
   114  	tests := []struct {
   115  		name             string
   116  		path             string
   117  		osrelease        string
   118  		wantPackages     []*extractor.Package
   119  		wantError        error
   120  		wantResultMetric stats.FileExtractedResult
   121  	}{
   122  		{
   123  			name:      "valid",
   124  			path:      "nix/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager-0.28/foo",
   125  			osrelease: NixVicuna,
   126  			wantPackages: []*extractor.Package{
   127  				{
   128  					Name:     "perl5.38.2-FCGI-ProcManager",
   129  					Version:  "0.28",
   130  					PURLType: purl.TypeNix,
   131  					Metadata: &nixmeta.Metadata{
   132  						PackageName:       "perl5.38.2-FCGI-ProcManager",
   133  						PackageVersion:    "0.28",
   134  						PackageHash:       "xakcaxsqdzjszym0vji2r8n0wdy2inqc",
   135  						PackageOutput:     "",
   136  						OSID:              "nixos",
   137  						OSVersionCodename: "vicuna",
   138  						OSVersionID:       "24.11",
   139  					},
   140  					Locations: []string{"nix/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager-0.28/foo"},
   141  				},
   142  			},
   143  			wantResultMetric: stats.FileExtractedResultSuccess,
   144  		},
   145  		{
   146  			name:      "valid",
   147  			path:      "nix/store/q5dhwzcn82by5ndc7g0q83wsnn13qkqw-webdav-server-rs-unstable-2021-08-16/foo",
   148  			osrelease: NixVicuna,
   149  			wantPackages: []*extractor.Package{
   150  				{
   151  					Name:     "webdav-server-rs",
   152  					Version:  "unstable-2021-08-16",
   153  					PURLType: purl.TypeNix,
   154  					Metadata: &nixmeta.Metadata{
   155  						PackageName:       "webdav-server-rs",
   156  						PackageVersion:    "unstable-2021-08-16",
   157  						PackageHash:       "q5dhwzcn82by5ndc7g0q83wsnn13qkqw",
   158  						PackageOutput:     "",
   159  						OSID:              "nixos",
   160  						OSVersionCodename: "vicuna",
   161  						OSVersionID:       "24.11",
   162  					},
   163  					Locations: []string{"nix/store/q5dhwzcn82by5ndc7g0q83wsnn13qkqw-webdav-server-rs-unstable-2021-08-16/foo"},
   164  				},
   165  			},
   166  			wantResultMetric: stats.FileExtractedResultSuccess,
   167  		},
   168  		{
   169  			name:         "invalid package hash",
   170  			path:         "nix/store/foo-webdav-server-rs-unstable-2021-08-16/foo",
   171  			osrelease:    NixVicuna,
   172  			wantPackages: nil,
   173  		},
   174  		{
   175  			name:         "no package name",
   176  			path:         "nix/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-0.28/foo",
   177  			osrelease:    NixVicuna,
   178  			wantPackages: nil,
   179  		},
   180  		{
   181  			name:         "no package version",
   182  			path:         "nix/store/xakcaxsqdzjszym0vji2r8n0wdy2inqc-perl5.38.2-FCGI-ProcManager/foo",
   183  			osrelease:    NixVicuna,
   184  			wantPackages: nil,
   185  		},
   186  		{
   187  			name:         "invalid",
   188  			path:         "nix/store/xzlmnp0lblcbscy36nlgif3js4mc68gm-base-system/etc/group",
   189  			osrelease:    NixVicuna,
   190  			wantPackages: nil,
   191  		},
   192  		{
   193  			name:         "invalid",
   194  			path:         "nix/store/a-b-c-d-e/foo",
   195  			osrelease:    NixVicuna,
   196  			wantPackages: nil,
   197  		},
   198  	}
   199  
   200  	for _, tt := range tests {
   201  		t.Run(tt.name, func(t *testing.T) {
   202  			var e = nix.New()
   203  
   204  			d := t.TempDir()
   205  			createOsRelease(t, d, tt.osrelease)
   206  
   207  			input := &filesystem.ScanInput{
   208  				FS:     scalibrfs.DirFS(d),
   209  				Path:   tt.path,
   210  				Reader: nil,
   211  				Root:   d,
   212  				Info:   nil,
   213  			}
   214  
   215  			got, err := e.Extract(t.Context(), input)
   216  
   217  			if err != nil {
   218  				t.Errorf("err = %v", err)
   219  			}
   220  
   221  			wantInv := inventory.Inventory{Packages: tt.wantPackages}
   222  			if diff := cmp.Diff(wantInv, got); diff != "" {
   223  				t.Errorf("Package mismatch (-want +got):\n%s", diff)
   224  			}
   225  		})
   226  	}
   227  }
   228  
   229  func createOsRelease(t *testing.T, root string, content string) {
   230  	t.Helper()
   231  	_ = os.MkdirAll(filepath.Join(root, "etc"), 0755)
   232  	err := os.WriteFile(filepath.Join(root, "etc/os-release"), []byte(content), 0644)
   233  	if err != nil {
   234  		t.Fatalf("write to %s: %v\n", filepath.Join(root, "etc/os-release"), err)
   235  	}
   236  }