github.com/google/cadvisor@v0.49.1/fs/fs_test.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     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 fs
    16  
    17  import (
    18  	"errors"
    19  	"os"
    20  	"reflect"
    21  	"testing"
    22  
    23  	mount "github.com/moby/sys/mountinfo"
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func TestMountInfoFromDir(t *testing.T) {
    29  	as := assert.New(t)
    30  	fsInfo := &RealFsInfo{
    31  		mounts: map[string]mount.Info{
    32  			"/": {},
    33  		},
    34  	}
    35  	testDirs := []string{"/var/lib/kubelet", "/var/lib/rancher"}
    36  	for _, testDir := range testDirs {
    37  		_, found := fsInfo.mountInfoFromDir(testDir)
    38  		as.True(found, "failed to find MountInfo %s from FsInfo %s", testDir, fsInfo)
    39  	}
    40  }
    41  
    42  func TestGetDiskStatsMap(t *testing.T) {
    43  	diskStatsMap, err := getDiskStatsMap("test_resources/diskstats")
    44  	if err != nil {
    45  		t.Errorf("Error calling getDiskStatsMap %s", err)
    46  	}
    47  	if len(diskStatsMap) != 30 {
    48  		t.Errorf("diskStatsMap %+v not valid", diskStatsMap)
    49  	}
    50  	keySet := map[string]string{
    51  		"/dev/sda":  "/dev/sda",
    52  		"/dev/sdb":  "/dev/sdb",
    53  		"/dev/sdc":  "/dev/sdc",
    54  		"/dev/sdd":  "/dev/sdd",
    55  		"/dev/sde":  "/dev/sde",
    56  		"/dev/sdf":  "/dev/sdf",
    57  		"/dev/sdg":  "/dev/sdg",
    58  		"/dev/sdh":  "/dev/sdh",
    59  		"/dev/sdb1": "/dev/sdb1",
    60  		"/dev/sdb2": "/dev/sdb2",
    61  		"/dev/sda1": "/dev/sda1",
    62  		"/dev/sda2": "/dev/sda2",
    63  		"/dev/sdc1": "/dev/sdc1",
    64  		"/dev/sdc2": "/dev/sdc2",
    65  		"/dev/sdc3": "/dev/sdc3",
    66  		"/dev/sdc4": "/dev/sdc4",
    67  		"/dev/sdd1": "/dev/sdd1",
    68  		"/dev/sdd2": "/dev/sdd2",
    69  		"/dev/sdd3": "/dev/sdd3",
    70  		"/dev/sdd4": "/dev/sdd4",
    71  		"/dev/sde1": "/dev/sde1",
    72  		"/dev/sde2": "/dev/sde2",
    73  		"/dev/sdf1": "/dev/sdf1",
    74  		"/dev/sdf2": "/dev/sdf2",
    75  		"/dev/sdg1": "/dev/sdg1",
    76  		"/dev/sdg2": "/dev/sdg2",
    77  		"/dev/sdh1": "/dev/sdh1",
    78  		"/dev/sdh2": "/dev/sdh2",
    79  		"/dev/dm-0": "/dev/dm-0",
    80  		"/dev/dm-1": "/dev/dm-1",
    81  	}
    82  
    83  	for device := range diskStatsMap {
    84  		if _, ok := keySet[device]; !ok {
    85  			t.Errorf("Cannot find device %s", device)
    86  		}
    87  		delete(keySet, device)
    88  	}
    89  	if len(keySet) != 0 {
    90  		t.Errorf("diskStatsMap %+v contains illegal keys %+v", diskStatsMap, keySet)
    91  	}
    92  }
    93  
    94  func TestGetDiskStatsMapMajorMinorNum(t *testing.T) {
    95  	diskStatsMap, err := getDiskStatsMap("test_resources/diskstats")
    96  	if err != nil {
    97  		t.Errorf("Error calling getDiskStatsMap %s", err)
    98  	}
    99  	if len(diskStatsMap) != 30 {
   100  		t.Errorf("diskStatsMap %+v not valid", diskStatsMap)
   101  	}
   102  
   103  	if stat, ok := diskStatsMap["/dev/dm-0"]; ok {
   104  		if stat.MajorNum != 252 && stat.MinorNum != 1 {
   105  			t.Fatalf("getDiskStatsMap did not return correct major (%d) and minor (%d) numbers", stat.MajorNum, stat.MinorNum)
   106  		}
   107  	}
   108  }
   109  
   110  func TestFileNotExist(t *testing.T) {
   111  	_, err := getDiskStatsMap("/file_does_not_exist")
   112  	if err != nil {
   113  		t.Fatalf("getDiskStatsMap must not error for absent file: %s", err)
   114  	}
   115  }
   116  
   117  func TestDirDiskUsage(t *testing.T) {
   118  	as := assert.New(t)
   119  	fsInfo, err := NewFsInfo(Context{})
   120  	as.NoError(err)
   121  	dir := t.TempDir()
   122  	as.NoError(err)
   123  	defer os.RemoveAll(dir)
   124  	dataSize := 1024 * 100 //100 KB
   125  	b := make([]byte, dataSize)
   126  	f, err := os.CreateTemp(dir, "")
   127  	as.NoError(err)
   128  	as.NoError(os.WriteFile(f.Name(), b, 0700))
   129  	fi, err := f.Stat()
   130  	as.NoError(err)
   131  	expectedSize := uint64(fi.Size())
   132  	usage, err := fsInfo.GetDirUsage(dir)
   133  	as.NoError(err)
   134  	as.True(expectedSize <= usage.Bytes, "expected dir size to be at-least %d; got size: %d", expectedSize, usage.Bytes)
   135  }
   136  
   137  func TestDirInodeUsage(t *testing.T) {
   138  	as := assert.New(t)
   139  	fsInfo, err := NewFsInfo(Context{})
   140  	as.NoError(err)
   141  	dir := t.TempDir()
   142  	defer os.RemoveAll(dir)
   143  	numFiles := 1000
   144  	for i := 0; i < numFiles; i++ {
   145  		_, err := os.MkdirTemp(dir, "")
   146  		require.NoError(t, err)
   147  	}
   148  	usage, err := fsInfo.GetDirUsage(dir)
   149  	as.NoError(err)
   150  	// We sould get numFiles+1 inodes, since we get 1 inode for each file, plus 1 for the directory
   151  	as.True(uint64(numFiles+1) == usage.Inodes, "expected inodes in dir to be %d; got inodes: %d", numFiles+1, usage.Inodes)
   152  }
   153  
   154  var dmStatusTests = []struct {
   155  	dmStatus    string
   156  	used        uint64
   157  	total       uint64
   158  	errExpected bool
   159  }{
   160  	{`0 409534464 thin-pool 64085 3705/4161600 88106/3199488 - rw no_discard_passdown queue_if_no_space -`, 88106, 3199488, false},
   161  	{`0 209715200 thin-pool 707 1215/524288 30282/1638400 - rw discard_passdown`, 30282, 1638400, false},
   162  	{`Invalid status line`, 0, 0, false},
   163  }
   164  
   165  func TestParseDMStatus(t *testing.T) {
   166  	for _, tt := range dmStatusTests {
   167  		used, total, err := parseDMStatus(tt.dmStatus)
   168  		if tt.errExpected && err != nil {
   169  			t.Errorf("parseDMStatus(%q) expected error", tt.dmStatus)
   170  		}
   171  		if used != tt.used {
   172  			t.Errorf("parseDMStatus(%q) wrong used value => %q, want %q", tt.dmStatus, used, tt.used)
   173  		}
   174  		if total != tt.total {
   175  			t.Errorf("parseDMStatus(%q) wrong total value => %q, want %q", tt.dmStatus, total, tt.total)
   176  		}
   177  	}
   178  }
   179  
   180  var dmTableTests = []struct {
   181  	dmTable     string
   182  	major       uint
   183  	minor       uint
   184  	dataBlkSize uint
   185  	errExpected bool
   186  }{
   187  	{`0 409534464 thin-pool 253:6 253:7 128 32768 1 skip_block_zeroing`, 253, 7, 128, false},
   188  	{`0 409534464 thin-pool 253:6 258:9 512 32768 1 skip_block_zeroing otherstuff`, 258, 9, 512, false},
   189  	{`Invalid status line`, 0, 0, 0, false},
   190  }
   191  
   192  func TestParseDMTable(t *testing.T) {
   193  	for _, tt := range dmTableTests {
   194  		major, minor, dataBlkSize, err := parseDMTable(tt.dmTable)
   195  		if tt.errExpected && err != nil {
   196  			t.Errorf("parseDMTable(%q) expected error", tt.dmTable)
   197  		}
   198  		if major != tt.major {
   199  			t.Errorf("parseDMTable(%q) wrong major value => %q, want %q", tt.dmTable, major, tt.major)
   200  		}
   201  		if minor != tt.minor {
   202  			t.Errorf("parseDMTable(%q) wrong minor value => %q, want %q", tt.dmTable, minor, tt.minor)
   203  		}
   204  		if dataBlkSize != tt.dataBlkSize {
   205  			t.Errorf("parseDMTable(%q) wrong dataBlkSize value => %q, want %q", tt.dmTable, dataBlkSize, tt.dataBlkSize)
   206  		}
   207  	}
   208  }
   209  
   210  func TestAddSystemRootLabel(t *testing.T) {
   211  	tests := []struct {
   212  		mounts   []*mount.Info
   213  		expected string
   214  	}{
   215  		{
   216  			mounts: []*mount.Info{
   217  				{Source: "/dev/sda1", Mountpoint: "/foo"},
   218  				{Source: "/dev/sdb1", Mountpoint: "/"},
   219  			},
   220  			expected: "/dev/sdb1",
   221  		},
   222  	}
   223  
   224  	for i, tt := range tests {
   225  		fsInfo := &RealFsInfo{
   226  			labels:     map[string]string{},
   227  			partitions: map[string]partition{},
   228  		}
   229  		fsInfo.addSystemRootLabel(tt.mounts)
   230  
   231  		if source, ok := fsInfo.labels[LabelSystemRoot]; !ok || source != tt.expected {
   232  			t.Errorf("case %d: expected mount source '%s', got '%s'", i, tt.expected, source)
   233  		}
   234  	}
   235  }
   236  
   237  type testDmsetup struct {
   238  	data []byte
   239  	err  error
   240  }
   241  
   242  func (*testDmsetup) Message(deviceName string, sector int, message string) ([]byte, error) {
   243  	return nil, nil
   244  }
   245  
   246  func (*testDmsetup) Status(deviceName string) ([]byte, error) {
   247  	return nil, nil
   248  }
   249  
   250  func (t *testDmsetup) Table(poolName string) ([]byte, error) {
   251  	return t.data, t.err
   252  }
   253  
   254  func TestGetDockerDeviceMapperInfo(t *testing.T) {
   255  	tests := []struct {
   256  		name              string
   257  		driver            string
   258  		driverStatus      map[string]string
   259  		dmsetupTable      string
   260  		dmsetupTableError error
   261  		expectedDevice    string
   262  		expectedPartition *partition
   263  		expectedError     bool
   264  	}{
   265  		{
   266  			name:              "not devicemapper",
   267  			driver:            "btrfs",
   268  			expectedDevice:    "",
   269  			expectedPartition: nil,
   270  			expectedError:     false,
   271  		},
   272  		{
   273  			name:              "nil driver status",
   274  			driver:            "devicemapper",
   275  			driverStatus:      nil,
   276  			expectedDevice:    "",
   277  			expectedPartition: nil,
   278  			expectedError:     true,
   279  		},
   280  		{
   281  			name:              "loopback",
   282  			driver:            "devicemapper",
   283  			driverStatus:      map[string]string{"Data loop file": "/var/lib/docker/devicemapper/devicemapper/data"},
   284  			expectedDevice:    "",
   285  			expectedPartition: nil,
   286  			expectedError:     false,
   287  		},
   288  		{
   289  			name:              "missing pool name",
   290  			driver:            "devicemapper",
   291  			driverStatus:      map[string]string{},
   292  			expectedDevice:    "",
   293  			expectedPartition: nil,
   294  			expectedError:     true,
   295  		},
   296  		{
   297  			name:              "error invoking dmsetup",
   298  			driver:            "devicemapper",
   299  			driverStatus:      map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
   300  			dmsetupTableError: errors.New("foo"),
   301  			expectedDevice:    "",
   302  			expectedPartition: nil,
   303  			expectedError:     true,
   304  		},
   305  		{
   306  			name:              "unable to parse dmsetup table",
   307  			driver:            "devicemapper",
   308  			driverStatus:      map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
   309  			dmsetupTable:      "no data here!",
   310  			expectedDevice:    "",
   311  			expectedPartition: nil,
   312  			expectedError:     true,
   313  		},
   314  		{
   315  			name:           "happy path",
   316  			driver:         "devicemapper",
   317  			driverStatus:   map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
   318  			dmsetupTable:   "0 53870592 thin-pool 253:2 253:3 1024 0 1 skip_block_zeroing",
   319  			expectedDevice: "vg_vagrant-docker--pool",
   320  			expectedPartition: &partition{
   321  				fsType:    "devicemapper",
   322  				major:     253,
   323  				minor:     3,
   324  				blockSize: 1024,
   325  			},
   326  			expectedError: false,
   327  		},
   328  	}
   329  
   330  	for _, tt := range tests {
   331  		fsInfo := &RealFsInfo{
   332  			dmsetup: &testDmsetup{
   333  				data: []byte(tt.dmsetupTable),
   334  			},
   335  		}
   336  
   337  		dockerCtx := DockerContext{
   338  			Driver:       tt.driver,
   339  			DriverStatus: tt.driverStatus,
   340  		}
   341  
   342  		device, partition, err := fsInfo.getDockerDeviceMapperInfo(dockerCtx)
   343  
   344  		if tt.expectedError && err == nil {
   345  			t.Errorf("%s: expected error but got nil", tt.name)
   346  			continue
   347  		}
   348  		if !tt.expectedError && err != nil {
   349  			t.Errorf("%s: unexpected error: %v", tt.name, err)
   350  			continue
   351  		}
   352  
   353  		if e, a := tt.expectedDevice, device; e != a {
   354  			t.Errorf("%s: device: expected %q, got %q", tt.name, e, a)
   355  		}
   356  
   357  		if e, a := tt.expectedPartition, partition; !reflect.DeepEqual(e, a) {
   358  			t.Errorf("%s: partition: expected %#v, got %#v", tt.name, e, a)
   359  		}
   360  	}
   361  }
   362  
   363  func TestAddDockerImagesLabel(t *testing.T) {
   364  	tests := []struct {
   365  		name                           string
   366  		driver                         string
   367  		driverStatus                   map[string]string
   368  		dmsetupTable                   string
   369  		getDockerDeviceMapperInfoError error
   370  		mounts                         []*mount.Info
   371  		expectedDockerDevice           string
   372  		expectedPartition              *partition
   373  	}{
   374  		{
   375  			name:   "single partition, no dedicated image fs",
   376  			driver: "overlay2",
   377  			mounts: []*mount.Info{
   378  				{
   379  					Source:     "/dev/root",
   380  					Mountpoint: "/",
   381  					FSType:     "ext4",
   382  				},
   383  				{
   384  					Source:     "/sys/fs/cgroup",
   385  					Mountpoint: "/sys/fs/cgroup",
   386  					FSType:     "tmpfs",
   387  				},
   388  			},
   389  			expectedDockerDevice: "/dev/root",
   390  		},
   391  		{
   392  			name:         "devicemapper, not loopback",
   393  			driver:       "devicemapper",
   394  			driverStatus: map[string]string{"Pool Name": "vg_vagrant-docker--pool"},
   395  			dmsetupTable: "0 53870592 thin-pool 253:2 253:3 1024 0 1 skip_block_zeroing",
   396  			mounts: []*mount.Info{
   397  				{
   398  					Source:     "/dev/mapper/vg_vagrant-lv_root",
   399  					Mountpoint: "/",
   400  					FSType:     "devicemapper",
   401  				},
   402  			},
   403  			expectedDockerDevice: "vg_vagrant-docker--pool",
   404  			expectedPartition: &partition{
   405  				fsType:    "devicemapper",
   406  				major:     253,
   407  				minor:     3,
   408  				blockSize: 1024,
   409  			},
   410  		},
   411  		{
   412  			name:         "devicemapper, loopback on non-root partition",
   413  			driver:       "devicemapper",
   414  			driverStatus: map[string]string{"Data loop file": "/var/lib/docker/devicemapper/devicemapper/data"},
   415  			mounts: []*mount.Info{
   416  				{
   417  					Source:     "/dev/mapper/vg_vagrant-lv_root",
   418  					Mountpoint: "/",
   419  					FSType:     "devicemapper",
   420  				},
   421  				{
   422  					Source:     "/dev/sdb1",
   423  					Mountpoint: "/var/lib/docker/devicemapper",
   424  				},
   425  			},
   426  			expectedDockerDevice: "/dev/sdb1",
   427  		},
   428  		{
   429  			name:   "multiple mounts - innermost check",
   430  			driver: "overlay2",
   431  			mounts: []*mount.Info{
   432  				{
   433  					Source:     "/dev/sda1",
   434  					Mountpoint: "/",
   435  					FSType:     "ext4",
   436  				},
   437  				{
   438  					Source:     "/dev/sdb1",
   439  					Mountpoint: "/var/lib/docker",
   440  					FSType:     "ext4",
   441  				},
   442  				{
   443  					Source:     "/dev/sdb2",
   444  					Mountpoint: "/var/lib/docker/btrfs",
   445  					FSType:     "btrfs",
   446  				},
   447  			},
   448  			expectedDockerDevice: "/dev/sdb2",
   449  		},
   450  		{
   451  			name:   "root fs inside container, docker-images bindmount",
   452  			driver: "overlay2",
   453  			mounts: []*mount.Info{
   454  				{
   455  					Source:     "overlay",
   456  					Mountpoint: "/",
   457  					FSType:     "overlay",
   458  				},
   459  				{
   460  					Source:     "/dev/sda1",
   461  					Mountpoint: "/var/lib/docker",
   462  					FSType:     "ext4",
   463  				},
   464  			},
   465  			expectedDockerDevice: "/dev/sda1",
   466  		},
   467  		{
   468  			name:   "[overlay2] root fs inside container - /var/lib/docker bindmount",
   469  			driver: "overlay2",
   470  			mounts: []*mount.Info{
   471  				{
   472  					Source:     "overlay",
   473  					Mountpoint: "/",
   474  					FSType:     "overlay",
   475  				},
   476  				{
   477  					Source:     "/dev/sdb1",
   478  					Mountpoint: "/var/lib/docker",
   479  					FSType:     "ext4",
   480  				},
   481  				{
   482  					Source:     "/dev/sdb2",
   483  					Mountpoint: "/var/lib/docker/overlay2",
   484  					FSType:     "ext4",
   485  				},
   486  			},
   487  			expectedDockerDevice: "/dev/sdb2",
   488  		},
   489  	}
   490  
   491  	for _, tt := range tests {
   492  		fsInfo := &RealFsInfo{
   493  			labels:     map[string]string{},
   494  			partitions: map[string]partition{},
   495  			dmsetup: &testDmsetup{
   496  				data: []byte(tt.dmsetupTable),
   497  			},
   498  		}
   499  
   500  		context := Context{
   501  			Docker: DockerContext{
   502  				Root:         "/var/lib/docker",
   503  				Driver:       tt.driver,
   504  				DriverStatus: tt.driverStatus,
   505  			},
   506  		}
   507  
   508  		fsInfo.addDockerImagesLabel(context, tt.mounts)
   509  
   510  		if e, a := tt.expectedDockerDevice, fsInfo.labels[LabelDockerImages]; e != a {
   511  			t.Errorf("%s: docker device: expected %q, got %q", tt.name, e, a)
   512  		}
   513  
   514  		if tt.expectedPartition == nil {
   515  			continue
   516  		}
   517  		if e, a := *tt.expectedPartition, fsInfo.partitions[tt.expectedDockerDevice]; !reflect.DeepEqual(e, a) {
   518  			t.Errorf("%s: docker partition: expected %#v, got %#v", tt.name, e, a)
   519  		}
   520  	}
   521  }
   522  
   523  func TestAddCrioImagesLabel(t *testing.T) {
   524  	tests := []struct {
   525  		name                   string
   526  		driver                 string
   527  		driverStatus           map[string]string
   528  		dmsetupTable           string
   529  		mounts                 []*mount.Info
   530  		imageStore             string
   531  		expectedCrioImages     string
   532  		expectedCrioContainers string
   533  		expectedPartition      *partition
   534  	}{
   535  		{
   536  			name:   "single partition, no dedicated image fs",
   537  			driver: "overlay2",
   538  			mounts: []*mount.Info{
   539  				{
   540  					Source:     "/dev/root",
   541  					Mountpoint: "/",
   542  					FSType:     "ext4",
   543  				},
   544  				{
   545  					Source:     "/sys/fs/cgroup",
   546  					Mountpoint: "/sys/fs/cgroup",
   547  					FSType:     "tmpfs",
   548  				},
   549  			},
   550  			expectedCrioImages:     "/dev/root",
   551  			expectedCrioContainers: "",
   552  		},
   553  		{
   554  			name:   "root fs inside container, docker-images bindmount",
   555  			driver: "overlay2",
   556  			mounts: []*mount.Info{
   557  				{
   558  					Source:     "overlay",
   559  					Mountpoint: "/",
   560  					FSType:     "overlay",
   561  				},
   562  				{
   563  					Source:     "/dev/sda1",
   564  					Mountpoint: "/var/lib/container",
   565  					FSType:     "ext4",
   566  				},
   567  			},
   568  			expectedCrioImages: "/dev/sda1",
   569  		},
   570  		{
   571  			name:   "[overlay2] image and container separate",
   572  			driver: "overlay2",
   573  			mounts: []*mount.Info{
   574  				{
   575  					Source:     "/dev/sdb1",
   576  					Mountpoint: "/imagestore",
   577  					FSType:     "ext4",
   578  				},
   579  				{
   580  					Source:     "/dev/sdb2",
   581  					Mountpoint: "/var/lib/container",
   582  					FSType:     "ext4",
   583  				},
   584  			},
   585  			expectedCrioImages:     "/dev/sdb1",
   586  			expectedCrioContainers: "/dev/sdb2",
   587  			imageStore:             "/imagestore",
   588  		},
   589  	}
   590  
   591  	for _, tt := range tests {
   592  		fsInfo := &RealFsInfo{
   593  			labels:     map[string]string{},
   594  			partitions: map[string]partition{},
   595  			dmsetup: &testDmsetup{
   596  				data: []byte(tt.dmsetupTable),
   597  			},
   598  		}
   599  
   600  		context := Context{
   601  			Crio: CrioContext{
   602  				Root:       "/var/lib/container",
   603  				ImageStore: tt.imageStore,
   604  				Driver:     "overlay",
   605  			},
   606  		}
   607  
   608  		fsInfo.addCrioImagesLabel(context, tt.mounts)
   609  
   610  		if tt.imageStore != "" {
   611  			if e, a := tt.expectedCrioImages, fsInfo.labels[LabelCrioImages]; e != a {
   612  				t.Errorf("%s: docker device: expected %q, got %q", tt.name, e, a)
   613  			}
   614  
   615  			if e, a := tt.expectedCrioContainers, fsInfo.labels[LabelCrioContainers]; e != a {
   616  				t.Errorf("%s: docker device: expected %q, got %q", tt.name, e, a)
   617  			}
   618  
   619  		}
   620  		if tt.imageStore == "" {
   621  			if e, a := tt.expectedCrioImages, fsInfo.labels[LabelCrioImages]; e != a {
   622  				t.Errorf("%s: docker device: expected %q, got %q", tt.name, e, a)
   623  			}
   624  
   625  		}
   626  
   627  		if tt.expectedPartition == nil {
   628  			continue
   629  		}
   630  	}
   631  }
   632  
   633  func TestProcessMounts(t *testing.T) {
   634  	tests := []struct {
   635  		name             string
   636  		mounts           []*mount.Info
   637  		excludedPrefixes []string
   638  		expected         map[string]partition
   639  	}{
   640  		{
   641  			name: "unsupported fs types",
   642  			mounts: []*mount.Info{
   643  				{FSType: "somethingelse"},
   644  			},
   645  			expected: map[string]partition{},
   646  		},
   647  		{
   648  			name: "avoid bind mounts",
   649  			mounts: []*mount.Info{
   650  				{Root: "/", Mountpoint: "/", Source: "/dev/sda1", FSType: "xfs", Major: 253, Minor: 0},
   651  				{Root: "/foo", Mountpoint: "/bar", Source: "/dev/sda1", FSType: "xfs", Major: 253, Minor: 0},
   652  			},
   653  			expected: map[string]partition{
   654  				"/dev/sda1": {fsType: "xfs", mountpoint: "/", major: 253, minor: 0},
   655  			},
   656  		},
   657  		{
   658  			name: "exclude prefixes",
   659  			mounts: []*mount.Info{
   660  				{Root: "/", Mountpoint: "/someother", Source: "/dev/sda1", FSType: "xfs", Major: 253, Minor: 2},
   661  				{Root: "/", Mountpoint: "/", Source: "/dev/sda2", FSType: "xfs", Major: 253, Minor: 0},
   662  				{Root: "/", Mountpoint: "/excludeme", Source: "/dev/sda3", FSType: "xfs", Major: 253, Minor: 1},
   663  			},
   664  			excludedPrefixes: []string{"/exclude", "/some"},
   665  			expected: map[string]partition{
   666  				"/dev/sda2": {fsType: "xfs", mountpoint: "/", major: 253, minor: 0},
   667  			},
   668  		},
   669  		{
   670  			name: "supported fs types",
   671  			mounts: []*mount.Info{
   672  				{Root: "/", Mountpoint: "/a", Source: "/dev/sda", FSType: "ext3", Major: 253, Minor: 0},
   673  				{Root: "/", Mountpoint: "/b", Source: "/dev/sdb", FSType: "ext4", Major: 253, Minor: 1},
   674  				{Root: "/", Mountpoint: "/c", Source: "/dev/sdc", FSType: "btrfs", Major: 253, Minor: 2},
   675  				{Root: "/", Mountpoint: "/d", Source: "/dev/sdd", FSType: "xfs", Major: 253, Minor: 3},
   676  				{Root: "/", Mountpoint: "/e", Source: "/dev/sde", FSType: "zfs", Major: 253, Minor: 4},
   677  				{Root: "/", Mountpoint: "/f", Source: "overlay", FSType: "overlay", Major: 253, Minor: 5},
   678  				{Root: "/", Mountpoint: "/g", Source: "127.0.0.1:/nfs", FSType: "nfs4", Major: 253, Minor: 6},
   679  				{Root: "/", Mountpoint: "/test1", Source: "tmpfs", FSType: "tmpfs", Major: 253, Minor: 4},
   680  				{Root: "/", Mountpoint: "/test2", Source: "tmpfs", FSType: "tmpfs", Major: 253, Minor: 4},
   681  			},
   682  			expected: map[string]partition{
   683  				"/dev/sda":       {fsType: "ext3", mountpoint: "/a", major: 253, minor: 0},
   684  				"/dev/sdb":       {fsType: "ext4", mountpoint: "/b", major: 253, minor: 1},
   685  				"/dev/sdc":       {fsType: "btrfs", mountpoint: "/c", major: 253, minor: 2},
   686  				"/dev/sdd":       {fsType: "xfs", mountpoint: "/d", major: 253, minor: 3},
   687  				"/dev/sde":       {fsType: "zfs", mountpoint: "/e", major: 253, minor: 4},
   688  				"overlay_253-5":  {fsType: "overlay", mountpoint: "/f", major: 253, minor: 5},
   689  				"127.0.0.1:/nfs": {fsType: "nfs4", mountpoint: "/g", major: 253, minor: 6},
   690  				"/test1":         {fsType: "tmpfs", mountpoint: "/test1", major: 253, minor: 4},
   691  				"/test2":         {fsType: "tmpfs", mountpoint: "/test2", major: 253, minor: 4},
   692  			},
   693  		},
   694  	}
   695  
   696  	for _, test := range tests {
   697  		actual := processMounts(test.mounts, test.excludedPrefixes)
   698  		if !reflect.DeepEqual(test.expected, actual) {
   699  			t.Errorf("%s: expected %#v, got %#v", test.name, test.expected, actual)
   700  		}
   701  	}
   702  }