github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/mapfs/fs_test.go (about)

     1  package mapfs_test
     2  
     3  import (
     4  	"io"
     5  	"io/fs"
     6  	"runtime"
     7  	"testing"
     8  
     9  	"github.com/samber/lo"
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/devseccon/trivy/pkg/mapfs"
    14  )
    15  
    16  type fileInfo struct {
    17  	name     string
    18  	fileMode fs.FileMode
    19  	isDir    bool
    20  	size     int64
    21  }
    22  
    23  var (
    24  	filePerm      = lo.Ternary(runtime.GOOS == "windows", fs.FileMode(0666), fs.FileMode(0644))
    25  	helloFileInfo = fileInfo{
    26  		name:     "hello.txt",
    27  		fileMode: filePerm,
    28  		isDir:    false,
    29  		size:     11,
    30  	}
    31  	btxtFileInfo = fileInfo{
    32  		name:     "b.txt",
    33  		fileMode: filePerm,
    34  		isDir:    false,
    35  		size:     3,
    36  	}
    37  	virtualFileInfo = fileInfo{
    38  		name:     "virtual.txt",
    39  		fileMode: 0600,
    40  		isDir:    false,
    41  		size:     7,
    42  	}
    43  	cdirFileInfo = fileInfo{
    44  		name:     "c",
    45  		fileMode: fs.FileMode(0700) | fs.ModeDir,
    46  		isDir:    true,
    47  		size:     256,
    48  	}
    49  )
    50  
    51  func initFS(t *testing.T) *mapfs.FS {
    52  	fsys := mapfs.New()
    53  	require.NoError(t, fsys.MkdirAll("a/b/c", 0700))
    54  	require.NoError(t, fsys.MkdirAll("a/b/empty", 0700))
    55  	require.NoError(t, fsys.WriteFile("hello.txt", "testdata/hello.txt"))
    56  	require.NoError(t, fsys.WriteFile("a/b/b.txt", "testdata/b.txt"))
    57  	require.NoError(t, fsys.WriteFile("a/b/c/c.txt", "testdata/c.txt"))
    58  	require.NoError(t, fsys.WriteFile("a/b/c/.dotfile", "testdata/dotfile"))
    59  	require.NoError(t, fsys.WriteVirtualFile("a/b/c/virtual.txt", []byte("virtual"), 0600))
    60  	return fsys
    61  }
    62  
    63  func assertFileInfo(t *testing.T, want fileInfo, got fs.FileInfo) {
    64  	if got == nil {
    65  		return
    66  	}
    67  	assert.Equal(t, want.name, got.Name())
    68  	assert.Equal(t, want.fileMode, got.Mode())
    69  	assert.Equal(t, want.isDir, got.Mode().IsDir())
    70  	assert.Equal(t, want.isDir, got.IsDir())
    71  	assert.Equal(t, want.size, got.Size())
    72  }
    73  
    74  func TestFS_Filter(t *testing.T) {
    75  	fsys := initFS(t)
    76  	t.Run("empty files", func(t *testing.T) {
    77  		newFS, err := fsys.Filter(nil)
    78  		require.NoError(t, err)
    79  		assert.Equal(t, fsys, newFS)
    80  	})
    81  	t.Run("happy", func(t *testing.T) {
    82  		newFS, err := fsys.Filter([]string{
    83  			"hello.txt",
    84  			"a/b/c/.dotfile",
    85  		})
    86  		require.NoError(t, err)
    87  		_, err = newFS.Stat("hello.txt")
    88  		require.ErrorIs(t, err, fs.ErrNotExist)
    89  		_, err = newFS.Stat("a/b/c/.dotfile")
    90  		require.ErrorIs(t, err, fs.ErrNotExist)
    91  		fi, err := newFS.Stat("a/b/c/c.txt")
    92  		require.NoError(t, err)
    93  		assert.Equal(t, "c.txt", fi.Name())
    94  	})
    95  }
    96  
    97  func TestFS_Stat(t *testing.T) {
    98  	tests := []struct {
    99  		name     string
   100  		filePath string
   101  		want     fileInfo
   102  		wantErr  assert.ErrorAssertionFunc
   103  	}{
   104  		{
   105  			name:     "ordinary file",
   106  			filePath: "hello.txt",
   107  			want:     helloFileInfo,
   108  			wantErr:  assert.NoError,
   109  		},
   110  		{
   111  			name:     "nested file",
   112  			filePath: "a/b/b.txt",
   113  			want:     btxtFileInfo,
   114  			wantErr:  assert.NoError,
   115  		},
   116  		{
   117  			name:     "virtual file",
   118  			filePath: "a/b/c/virtual.txt",
   119  			want:     virtualFileInfo,
   120  			wantErr:  assert.NoError,
   121  		},
   122  		{
   123  			name:     "dir",
   124  			filePath: "a/b/c",
   125  			want:     cdirFileInfo,
   126  			wantErr:  assert.NoError,
   127  		},
   128  		{
   129  			name:     "no such file",
   130  			filePath: "nosuch.txt",
   131  			wantErr:  assert.Error,
   132  		},
   133  	}
   134  
   135  	for _, tt := range tests {
   136  		fsys := initFS(t)
   137  		t.Run(tt.name, func(t *testing.T) {
   138  			got, err := fsys.Stat(tt.filePath)
   139  			tt.wantErr(t, err)
   140  			assertFileInfo(t, tt.want, got)
   141  		})
   142  	}
   143  }
   144  
   145  func TestFS_ReadDir(t *testing.T) {
   146  	type dirEntry struct {
   147  		name     string
   148  		fileMode fs.FileMode
   149  		isDir    bool
   150  		size     int64
   151  		fileInfo fileInfo
   152  	}
   153  
   154  	tests := []struct {
   155  		name     string
   156  		filePath string
   157  		want     []dirEntry
   158  		wantErr  assert.ErrorAssertionFunc
   159  	}{
   160  		{
   161  			name:     "at root",
   162  			filePath: ".",
   163  			want: []dirEntry{
   164  				{
   165  					name:     "a",
   166  					fileMode: fs.FileMode(0700) | fs.ModeDir,
   167  					isDir:    true,
   168  					size:     0x100,
   169  					fileInfo: fileInfo{
   170  						name:     "a",
   171  						fileMode: fs.FileMode(0700) | fs.ModeDir,
   172  						isDir:    true,
   173  						size:     0x100,
   174  					},
   175  				},
   176  				{
   177  					name:     "hello.txt",
   178  					fileMode: filePerm,
   179  					isDir:    false,
   180  					size:     11,
   181  					fileInfo: helloFileInfo,
   182  				},
   183  			},
   184  			wantErr: assert.NoError,
   185  		},
   186  		{
   187  			name:     "multiple files",
   188  			filePath: "a/b/c",
   189  			want: []dirEntry{
   190  				{
   191  					name:     ".dotfile",
   192  					fileMode: filePerm,
   193  					isDir:    false,
   194  					size:     7,
   195  					fileInfo: fileInfo{
   196  						name:     ".dotfile",
   197  						fileMode: filePerm,
   198  						isDir:    false,
   199  						size:     7,
   200  					},
   201  				},
   202  				{
   203  					name:     "c.txt",
   204  					fileMode: filePerm,
   205  					isDir:    false,
   206  					size:     0,
   207  					fileInfo: fileInfo{
   208  						name:     "c.txt",
   209  						fileMode: filePerm,
   210  						isDir:    false,
   211  						size:     0,
   212  					},
   213  				},
   214  				{
   215  					name:     "virtual.txt",
   216  					fileMode: 0600,
   217  					isDir:    false,
   218  					size:     0,
   219  					fileInfo: virtualFileInfo,
   220  				},
   221  			},
   222  			wantErr: assert.NoError,
   223  		},
   224  		{
   225  			name:     "no such dir",
   226  			filePath: "nosuch/",
   227  			wantErr:  assert.Error,
   228  		},
   229  	}
   230  
   231  	for _, tt := range tests {
   232  		fsys := initFS(t)
   233  		t.Run(tt.name, func(t *testing.T) {
   234  			entries, err := fsys.ReadDir(tt.filePath)
   235  			tt.wantErr(t, err)
   236  
   237  			for _, z := range lo.Zip2(entries, tt.want) {
   238  				got, want := z.A, z.B
   239  				assert.Equal(t, want.name, got.Name())
   240  				assert.Equal(t, want.fileMode, got.Type(), want.name)
   241  				assert.Equal(t, want.isDir, got.IsDir(), want.name)
   242  
   243  				fi, err := got.Info()
   244  				require.NoError(t, err)
   245  				assertFileInfo(t, want.fileInfo, fi)
   246  			}
   247  		})
   248  	}
   249  }
   250  
   251  func TestFS_Open(t *testing.T) {
   252  	type file struct {
   253  		fileInfo fileInfo
   254  		body     string
   255  	}
   256  
   257  	tests := []struct {
   258  		name     string
   259  		filePath string
   260  		want     file
   261  		wantErr  assert.ErrorAssertionFunc
   262  	}{
   263  		{
   264  			name:     "ordinary file",
   265  			filePath: "hello.txt",
   266  			want: file{
   267  				fileInfo: helloFileInfo,
   268  				body:     "hello world",
   269  			},
   270  			wantErr: assert.NoError,
   271  		},
   272  		{
   273  			name:     "virtual file",
   274  			filePath: "a/b/c/virtual.txt",
   275  			want: file{
   276  				fileInfo: virtualFileInfo,
   277  				body:     "virtual",
   278  			},
   279  			wantErr: assert.NoError,
   280  		},
   281  		{
   282  			name:     "dir",
   283  			filePath: "a/b/c",
   284  			want: file{
   285  				fileInfo: cdirFileInfo,
   286  			},
   287  			wantErr: assert.NoError,
   288  		},
   289  		{
   290  			name:     "no such file",
   291  			filePath: "nosuch.txt",
   292  			wantErr:  assert.Error,
   293  		},
   294  	}
   295  
   296  	for _, tt := range tests {
   297  		fsys := initFS(t)
   298  		t.Run(tt.name, func(t *testing.T) {
   299  			f, err := fsys.Open(tt.filePath)
   300  			tt.wantErr(t, err)
   301  			if f == nil {
   302  				return
   303  			}
   304  			defer func() {
   305  				require.NoError(t, f.Close())
   306  			}()
   307  
   308  			fi, err := f.Stat()
   309  			require.NoError(t, err)
   310  			assertFileInfo(t, tt.want.fileInfo, fi)
   311  
   312  			if tt.want.body != "" {
   313  				b, err := io.ReadAll(f)
   314  				require.NoError(t, err)
   315  				assert.Equal(t, tt.want.body, string(b))
   316  			}
   317  		})
   318  	}
   319  }
   320  
   321  func TestFS_ReadFile(t *testing.T) {
   322  	tests := []struct {
   323  		name     string
   324  		filePath string
   325  		want     string
   326  		wantErr  assert.ErrorAssertionFunc
   327  	}{
   328  		{
   329  			name:     "ordinary file",
   330  			filePath: "hello.txt",
   331  			want:     "hello world",
   332  			wantErr:  assert.NoError,
   333  		},
   334  		{
   335  			name:     "virtual file",
   336  			filePath: "a/b/c/virtual.txt",
   337  			want:     "virtual",
   338  			wantErr:  assert.NoError,
   339  		},
   340  		{
   341  			name:     "no such file",
   342  			filePath: "nosuch.txt",
   343  			wantErr:  assert.Error,
   344  		},
   345  	}
   346  
   347  	for _, tt := range tests {
   348  		fsys := initFS(t)
   349  		t.Run(tt.name, func(t *testing.T) {
   350  			b, err := fsys.ReadFile(tt.filePath)
   351  			tt.wantErr(t, err)
   352  			assert.Equal(t, tt.want, string(b))
   353  		})
   354  	}
   355  }
   356  
   357  func TestFS_Sub(t *testing.T) {
   358  	fsys := initFS(t)
   359  	sub, err := fsys.Sub("a/b")
   360  	require.NoError(t, err)
   361  
   362  	data, err := sub.(fs.ReadFileFS).ReadFile("c/.dotfile")
   363  	require.NoError(t, err)
   364  	assert.Equal(t, "dotfile", string(data))
   365  }
   366  
   367  func TestFS_Glob(t *testing.T) {
   368  	tests := []struct {
   369  		name    string
   370  		pattern string
   371  		want    []string
   372  		wantErr assert.ErrorAssertionFunc
   373  	}{
   374  		{
   375  			name:    "root",
   376  			pattern: "*",
   377  			want: []string{
   378  				"a",
   379  				"hello.txt",
   380  			},
   381  			wantErr: assert.NoError,
   382  		},
   383  		{
   384  			name:    "pattern",
   385  			pattern: "*/b/c/*.txt",
   386  			want: []string{
   387  				"a/b/c/c.txt",
   388  				"a/b/c/virtual.txt",
   389  			},
   390  			wantErr: assert.NoError,
   391  		},
   392  		{
   393  			name:    "no such",
   394  			pattern: "nosuch",
   395  			wantErr: assert.NoError,
   396  		},
   397  	}
   398  
   399  	for _, tt := range tests {
   400  		fsys := initFS(t)
   401  		t.Run(tt.name, func(t *testing.T) {
   402  			results, err := fsys.Glob(tt.pattern)
   403  			tt.wantErr(t, err)
   404  			assert.Equal(t, tt.want, results)
   405  		})
   406  	}
   407  }
   408  
   409  func TestFS_Remove(t *testing.T) {
   410  	tests := []struct {
   411  		name    string
   412  		path    string
   413  		wantErr assert.ErrorAssertionFunc
   414  	}{
   415  		{
   416  			name:    "ordinary file",
   417  			path:    "hello.txt",
   418  			wantErr: assert.NoError,
   419  		},
   420  		{
   421  			name:    "nested file",
   422  			path:    "a/b/b.txt",
   423  			wantErr: assert.NoError,
   424  		},
   425  		{
   426  			name:    "virtual file",
   427  			path:    "a/b/c/virtual.txt",
   428  			wantErr: assert.NoError,
   429  		},
   430  		{
   431  			name:    "empty dir",
   432  			path:    "a/b/empty",
   433  			wantErr: assert.NoError,
   434  		},
   435  		{
   436  			name:    "empty path",
   437  			path:    "",
   438  			wantErr: assert.NoError,
   439  		},
   440  		{
   441  			name:    "non-empty dir",
   442  			path:    "a/b/c",
   443  			wantErr: assert.Error,
   444  		},
   445  	}
   446  
   447  	for _, tt := range tests {
   448  		fsys := initFS(t)
   449  		t.Run(tt.name, func(t *testing.T) {
   450  			err := fsys.Remove(tt.path)
   451  			tt.wantErr(t, err)
   452  			if err != nil || tt.path == "" {
   453  				return
   454  			}
   455  
   456  			_, err = fsys.Stat(tt.path)
   457  			require.ErrorIs(t, err, fs.ErrNotExist)
   458  		})
   459  	}
   460  }
   461  
   462  func TestFS_RemoveAll(t *testing.T) {
   463  	fsys := initFS(t)
   464  	t.Run("ordinary file", func(t *testing.T) {
   465  		err := fsys.RemoveAll("hello.txt")
   466  		require.NoError(t, err)
   467  		_, err = fsys.Stat("hello.txt")
   468  		require.ErrorIs(t, err, fs.ErrNotExist)
   469  	})
   470  	t.Run("non-empty dir", func(t *testing.T) {
   471  		err := fsys.RemoveAll("a/b")
   472  		require.NoError(t, err)
   473  		_, err = fsys.Stat("a/b/c/c.txt")
   474  		require.ErrorIs(t, err, fs.ErrNotExist)
   475  		_, err = fsys.Stat("a/b/c/.dotfile")
   476  		require.ErrorIs(t, err, fs.ErrNotExist)
   477  		_, err = fsys.Stat("a/b/c/virtual.txt")
   478  		require.ErrorIs(t, err, fs.ErrNotExist)
   479  	})
   480  }