diff --git a/.circleci/prepare_environment.sh b/.circleci/prepare_environment.sh index 1f21b48..5de08d3 100644 --- a/.circleci/prepare_environment.sh +++ b/.circleci/prepare_environment.sh @@ -5,10 +5,18 @@ set -e # Prepare environment for quota support apt-get update apt-get install -y quota -truncate -s1G /tmp/test.ext4 -mkfs.ext4 -F /tmp/test.ext4 -mkdir -p /mnt/quota_test -mount -o usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv1 /tmp/test.ext4 /mnt/quota_test +truncate -s1G /tmp/quotas_enabled.ext4 +truncate -s1G /tmp/quotas_disabled.ext4 +mkfs.ext4 -F /tmp/quotas_enabled.ext4 +mkfs.ext4 -F /tmp/quotas_disabled.ext4 +mkdir -p /mnt/quota_test /mnt/noquota_test +mount -o usrjquota=aquota.user,grpjquota=aquota.group,jqfmt=vfsv1 /tmp/quotas_enabled.ext4 /mnt/quota_test +mount /tmp/quotas_disabled.ext4 /mnt/noquota_test quotacheck -vucm /mnt/quota_test quotacheck -vugm /mnt/quota_test quotaon -v /mnt/quota_test +for i in {10000..10009} +do + addgroup --gid $i test$i + adduser --system --shell /bin/false --no-create-home --uid $i --gid $i --disabled-login --disabled-password test$i +done diff --git a/.circleci/run_tests.sh b/.circleci/run_tests.sh index 11e1801..0b90aeb 100644 --- a/.circleci/run_tests.sh +++ b/.circleci/run_tests.sh @@ -10,5 +10,7 @@ mkdir -p $GOPATH/src/github.com/anexia-it cp -r /home/circleci/project/ $GOPATH/src/github.com/anexia-it/fsquota cd $GOPATH/src/github.com/anexia-it/fsquota -TEST_MOUNTPOINT=/mnt/quota_test go test -v --covermode=atomic --coverprofile=/home/circleci/project/coverage.txt . +export TEST_MOUNTPOINT_QUOTAS_ENABLED=/mnt/quota_test +export TEST_MOUNTPOINT_QUOTAS_DISABLED=/mnt/noquota_test +go test -v --covermode=atomic --coverprofile=/home/circleci/project/coverage.txt . chmod 0644 /home/circleci/project/coverage.txt diff --git a/fsquota_linux.go b/fsquota_linux.go index 8645023..c08f32a 100644 --- a/fsquota_linux.go +++ b/fsquota_linux.go @@ -211,7 +211,7 @@ func (n nextdqblk) toDqblk() *dqblk { } func getReportByNextQuota(t quotaCtlType, device string) (report *Report, err error) { - report = &Report{ + rep := &Report{ Infos: make(map[string]*Info), } @@ -232,10 +232,14 @@ func getReportByNextQuota(t quotaCtlType, device string) (report *Report, err er break } - report.Infos[fmt.Sprint(nextQuotaInfoStruct.dqbId)] = nextQuotaInfoStruct.toDqblk().toInfo() + rep.Infos[fmt.Sprint(nextQuotaInfoStruct.dqbId)] = nextQuotaInfoStruct.toDqblk().toInfo() nextId += 1 } + if err == nil { + report = rep + } + return } @@ -274,7 +278,7 @@ func quotasSupported(t quotaCtlType, path string) (supported bool, err error) { return } - if _, err = internalGetQuota(groupQuota, device, 0); err == nil { + if _, err = internalGetQuota(t, device, 0); err == nil { supported = true } diff --git a/info_test.go b/info_test.go new file mode 100644 index 0000000..28dacd8 --- /dev/null +++ b/info_test.go @@ -0,0 +1,76 @@ +package fsquota + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestInfoIsEmpty(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + i := Info{} + assert.True(t, i.isEmpty()) + }) + + t.Run("BytesSoftSet", func(t *testing.T) { + l := uint64(1) + i := Info{ + Limits: Limits{ + Bytes: Limit{ + soft: &l, + }, + }, + } + assert.False(t, i.isEmpty()) + }) + + t.Run("BytesHardSet", func(t *testing.T) { + l := uint64(1) + i := Info{ + Limits: Limits{ + Bytes: Limit{ + hard: &l, + }, + }, + } + assert.False(t, i.isEmpty()) + }) + + t.Run("BytesUsedSet", func(t *testing.T) { + i := Info{ + BytesUsed: 1, + } + assert.False(t, i.isEmpty()) + }) + + t.Run("FilesSoftSet", func(t *testing.T) { + l := uint64(1) + i := Info{ + Limits: Limits{ + Files: Limit{ + soft: &l, + }, + }, + } + assert.False(t, i.isEmpty()) + }) + + t.Run("FilesHardSet", func(t *testing.T) { + l := uint64(1) + i := Info{ + Limits: Limits{ + Files: Limit{ + hard: &l, + }, + }, + } + assert.False(t, i.isEmpty()) + }) + + t.Run("FilesUsedSet", func(t *testing.T) { + i := Info{ + FilesUsed: 1, + } + assert.False(t, i.isEmpty()) + }) +} diff --git a/integration_linux_test.go b/integration_linux_test.go index 3b9351e..cf162c6 100644 --- a/integration_linux_test.go +++ b/integration_linux_test.go @@ -1,6 +1,8 @@ package fsquota_test import ( + "fmt" + "io/ioutil" "os" "os/user" "path/filepath" @@ -11,11 +13,14 @@ import ( "github.com/stretchr/testify/require" ) -func prepareIntegrationTest(t *testing.T) string { - testMountPoint := os.Getenv("TEST_MOUNTPOINT") +func prepareIntegrationTest(t *testing.T) (testMountpointQuotasEnabled, testMountpointQuotasDisabled string) { + testMountpointQuotasEnabled = os.Getenv("TEST_MOUNTPOINT_QUOTAS_ENABLED") + testMountpointQuotasDisabled = os.Getenv("TEST_MOUNTPOINT_QUOTAS_DISABLED") - if testMountPoint == "" { - t.Skip("Skipping integration tests: TEST_MOUNTPOINT environment variable not set") + if testMountpointQuotasEnabled == "" { + t.Skip("Skipping integration tests: TEST_MOUNTPOINT_QUOTAS_ENABLED environment variable not set") + } else if testMountpointQuotasDisabled == "" { + t.Skip("Skipping integration tests: TEST_MOUNTPOINT_QUOTAS_DISABLED environment variable not set") } else if os.Getuid() != 0 { t.Skip("Skipping integration tests: not running as root") } @@ -25,13 +30,16 @@ func prepareIntegrationTest(t *testing.T) string { t.SkipNow() } - testMountPoint = filepath.Join(testMountPoint, "child") - require.NoError(t, os.MkdirAll(testMountPoint, 0755)) - return testMountPoint + testMountpointQuotasEnabled = filepath.Join(testMountpointQuotasEnabled, "child") + require.NoError(t, os.MkdirAll(testMountpointQuotasEnabled, 0755)) + + testMountpointQuotasDisabled = filepath.Join(testMountpointQuotasDisabled, "child") + require.NoError(t, os.MkdirAll(testMountpointQuotasDisabled, 0755)) + return } func TestSetAndGetUserQuota(t *testing.T) { - testMountPoint := prepareIntegrationTest(t) + testMountPointQuotasEnabled, testMountpointQuotasDisabled := prepareIntegrationTest(t) // Test against user 10000 testUser := &user.User{ @@ -44,29 +52,37 @@ func TestSetAndGetUserQuota(t *testing.T) { limits.Files.SetSoft(1000) // 1000 files soft limit limits.Files.SetHard(5000) // 5000 files hard limit - quotaInfo, err := fsquota.SetUserQuota(testMountPoint, testUser, limits) - require.NoError(t, err) - require.NotNil(t, quotaInfo) - - assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) - assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) - assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) - assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) - - // Retrieve the quota information again, testing GetUserQuota as well - quotaInfo, err = fsquota.GetUserInfo(testMountPoint, testUser) - require.NoError(t, err) - require.NotNil(t, quotaInfo) - - // The values should still be the same, meaning the information was persisted to the filesystem - assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) - assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) - assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) - assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + t.Run("QuotasEnabled", func(t *testing.T) { + quotaInfo, err := fsquota.SetUserQuota(testMountPointQuotasEnabled, testUser, limits) + require.NoError(t, err) + require.NotNil(t, quotaInfo) + + assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) + assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) + assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) + assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + + // Retrieve the quota information again, testing GetUserQuota as well + quotaInfo, err = fsquota.GetUserInfo(testMountPointQuotasEnabled, testUser) + require.NoError(t, err) + require.NotNil(t, quotaInfo) + + // The values should still be the same, meaning the information was persisted to the filesystem + assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) + assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) + assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) + assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + }) + + t.Run("QuotasDisabled", func(t *testing.T) { + quotaInfo, err := fsquota.SetUserQuota(testMountpointQuotasDisabled, testUser, limits) + assert.Error(t, err) + assert.Nil(t, quotaInfo) + }) } func TestSetAndGetGroupQuota(t *testing.T) { - testMountPoint := prepareIntegrationTest(t) + testMountPointQuotasEnabled, testMountpointQuotasDisabled := prepareIntegrationTest(t) // Test against group 10000 testGroup := &user.Group{ @@ -79,48 +95,253 @@ func TestSetAndGetGroupQuota(t *testing.T) { limits.Files.SetSoft(1000) // 1000 files soft limit limits.Files.SetHard(5000) // 5000 files hard limit - quotaInfo, err := fsquota.SetGroupQuota(testMountPoint, testGroup, limits) - require.NoError(t, err) + t.Run("QuotasEnabled", func(t *testing.T) { + quotaInfo, err := fsquota.SetGroupQuota(testMountPointQuotasEnabled, testGroup, limits) + require.NoError(t, err) + + assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) + assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) + assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) + assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + + // Retrieve the quota information again, testing GetUserQuota as well + quotaInfo, err = fsquota.GetGroupInfo(testMountPointQuotasEnabled, testGroup) + require.NoError(t, err) + require.NotNil(t, quotaInfo) - assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) - assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) - assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) - assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + // The values should still be the same, meaning the information was persisted to the filesystem + assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) + assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) + assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) + assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + }) - // Retrieve the quota information again, testing GetUserQuota as well - quotaInfo, err = fsquota.GetGroupInfo(testMountPoint, testGroup) - require.NoError(t, err) - require.NotNil(t, quotaInfo) + t.Run("QuotasDisabled", func(t *testing.T) { + quotaInfo, err := fsquota.SetGroupQuota(testMountpointQuotasDisabled, testGroup, limits) + assert.Error(t, err) + assert.Nil(t, quotaInfo) + }) - // The values should still be the same, meaning the information was persisted to the filesystem - assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) - assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) - assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) - assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) } func TestUserQuotasSupported(t *testing.T) { - testMountPoint := prepareIntegrationTest(t) + testMountPointQuotasEnabled, testMountpointQuotasDisabled := prepareIntegrationTest(t) - supported, err := fsquota.UserQuotasSupported(testMountPoint) - assert.True(t, supported) - assert.NoError(t, err) + t.Run("QuotasEnabled", func(t *testing.T) { + supported, err := fsquota.UserQuotasSupported(testMountPointQuotasEnabled) + assert.True(t, supported) + assert.NoError(t, err) + }) - // We expect / not to have quota support enabled - supported, err = fsquota.UserQuotasSupported("/") - assert.False(t, supported) - assert.Error(t, err) + t.Run("QuotasDisabled", func(t *testing.T) { + supported, err := fsquota.UserQuotasSupported(testMountpointQuotasDisabled) + assert.False(t, supported) + assert.Error(t, err) + }) } func TestGroupQuotasSupported(t *testing.T) { - testMountPoint := prepareIntegrationTest(t) + testMountPointQuotasEnabled, testMountpointQuotasDisabled := prepareIntegrationTest(t) + + t.Run("QuotasEnabled", func(t *testing.T) { + supported, err := fsquota.GroupQuotasSupported(testMountPointQuotasEnabled) + assert.True(t, supported) + assert.NoError(t, err) + }) + + t.Run("QuotasDisabled", func(t *testing.T) { + supported, err := fsquota.GroupQuotasSupported(testMountpointQuotasDisabled) + assert.False(t, supported) + assert.Error(t, err) + }) +} + +func TestGetUserReport(t *testing.T) { + testMountPointQuotasEnabled, testMountPointQuotasDisabled := prepareIntegrationTest(t) + + const userCount = 10 + const uidBase = 10000 + + limits := fsquota.Limits{} + limits.Bytes.SetSoft(userCount * 1024 * 1024) // userCount MiB soft limit + limits.Bytes.SetHard(500 * 1024 * 1024) // 500MiB hard limit + limits.Files.SetSoft(1000) // 1000 files soft limit + limits.Files.SetHard(5000) // 5000 files hard limit + + t.Run("QuotasEnabled", func(t *testing.T) { + expectedUserByteUsages := make(map[int]int64, userCount) + + testDirectory := filepath.Join(testMountPointQuotasEnabled, "data_files") + require.NoError(t, os.MkdirAll(testDirectory, 0755)) + defer os.RemoveAll(testDirectory) + + for uid := uidBase; uid < uidBase+userCount; uid++ { + // Test against user 10000 + testUser := &user.User{ + Uid: fmt.Sprint(uid), + } + + // Configure quota for user + quotaInfo, err := fsquota.SetUserQuota(testMountPointQuotasEnabled, testUser, limits) + require.NoError(t, err, "Failed to set quota for UID %d", uid) + + assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) + assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) + assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) + assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + + // Create s sparse file for the user... + fileName := filepath.Join(testDirectory, fmt.Sprintf("user_%d.data", uid)) + fileSize := int64(((uid - uidBase) + 1) * 1024 * 1024) // user 0 gets 1MiB, user 1 gets 2MiB file, ... + expectedUserByteUsages[uid] = fileSize + + // Run creation logic in an anonymous function so we can defer close the file... + func() { + f, err := os.Create(fileName) + require.NoError(t, err) + defer f.Close() + + // Create data file... + data := make([]byte, fileSize) + _, err = f.Write(data) + require.NoError(t, err) + + // Change ownership of the file to the target user + require.NoError(t, os.Chown(fileName, uid, uid)) + }() + + // Create additional files for every user... + for i := uidBase; i < uid; i++ { + fileName := filepath.Join(testDirectory, fmt.Sprintf("user_%d.empty.%d", uid, i)) + require.NoError(t, ioutil.WriteFile(fileName, []byte{}, 0644), "Failed to write user %d file #%d", uid, i-uidBase) + require.NoError(t, os.Chown(fileName, uid, uid)) + } + } + + // Now generate the report + report, err := fsquota.GetUserReport(testMountPointQuotasEnabled) + require.NoError(t, err) + require.NotNil(t, report) + + // Check that the report for every user is correct... + for uid, expectedBytes := range expectedUserByteUsages { + userReport, userReportExists := report.Infos[fmt.Sprint(uid)] + + userReportFound := assert.True(t, userReportExists, "Report info for UID %d is missing", uid) + userReportNotNil := assert.NotNil(t, userReport, "Report for UID %d is nil", uid) + + if userReportFound && userReportNotNil { + assert.EqualValues(t, expectedBytes, userReport.BytesUsed, "Bytes used for UID %d is invalid. Expected %d, actual %d", uid, expectedBytes, userReport.BytesUsed) + assert.EqualValues(t, 1+(uid-uidBase), userReport.FilesUsed, "Files used for UID %d is invalid. Expected %d, actual %d", uid, 1+(uid-uidBase), userReport.FilesUsed) + + // Validate that the configured quotas are correct + assert.EqualValues(t, 10*1024*1024, userReport.Bytes.GetSoft(), "Byte soft quota for UID %d is invalid", uid) + assert.EqualValues(t, 500*1024*1024, userReport.Bytes.GetHard(), "Byte hard quota for UID %d is invalid", uid) + assert.EqualValues(t, 1000, userReport.Files.GetSoft(), "File soft quota for UID %d is invalid", uid) + assert.EqualValues(t, 5000, userReport.Files.GetHard(), "File hard quota for UID %d is invalid", uid) + } + + } + }) + + t.Run("QuotasDisabled", func(t *testing.T) { + report, err := fsquota.GetUserReport(testMountPointQuotasDisabled) + assert.Error(t, err) + assert.Nil(t, report) + }) +} + +func TestGetGroupReport(t *testing.T) { + testMountPointQuotasEnabled, testMountPointQuotasDisabled := prepareIntegrationTest(t) + + const groupCount = 10 + const gidBase = 10000 + + limits := fsquota.Limits{} + limits.Bytes.SetSoft(groupCount * 1024 * 1024) // groupCount MiB soft limit + limits.Bytes.SetHard(500 * 1024 * 1024) // 500MiB hard limit + limits.Files.SetSoft(1000) // 1000 files soft limit + limits.Files.SetHard(5000) // 5000 files hard limit + + t.Run("QuotasEnabled", func(t *testing.T) { + expectedGroupByteUsages := make(map[int]int64, groupCount) + + testDirectory := filepath.Join(testMountPointQuotasEnabled, "data_files") + require.NoError(t, os.MkdirAll(testDirectory, 0755)) + defer os.RemoveAll(testDirectory) + + for gid := gidBase; gid < gidBase+groupCount; gid++ { + // Test against group + testGroup := &user.Group{ + Gid: fmt.Sprint(gid), + } + + // Configure quota for group + quotaInfo, err := fsquota.SetGroupQuota(testMountPointQuotasEnabled, testGroup, limits) + require.NoError(t, err, "Failed to set quota for GID %d", gid) + + assert.EqualValues(t, 10*1024*1024, quotaInfo.Bytes.GetSoft()) + assert.EqualValues(t, 500*1024*1024, quotaInfo.Bytes.GetHard()) + assert.EqualValues(t, 1000, quotaInfo.Files.GetSoft()) + assert.EqualValues(t, 5000, quotaInfo.Files.GetHard()) + + // Create s sparse file for the user... + fileName := filepath.Join(testDirectory, fmt.Sprintf("group_%d.data", gid)) + fileSize := int64(((gid - gidBase) + 1) * 1024 * 1024) // group 0 gets 1MiB, group 1 gets 2MiB file, ... + expectedGroupByteUsages[gid] = fileSize + + // Run creation logic in an anonymous function so we can defer close the file... + func() { + f, err := os.Create(fileName) + require.NoError(t, err) + defer f.Close() + + // Create data file... + data := make([]byte, fileSize) + _, err = f.Write(data) + require.NoError(t, err) + + // Change ownership of the file to the target group + require.NoError(t, os.Chown(fileName, gid, gid)) + }() + + // Create additional files for every user... + for i := gidBase; i < gid; i++ { + fileName := filepath.Join(testDirectory, fmt.Sprintf("group_%d.empty.%d", gid, i)) + require.NoError(t, ioutil.WriteFile(fileName, []byte{}, 0644), "Failed to write group %d file #%d", gid, i-gidBase) + require.NoError(t, os.Chown(fileName, gid, gid)) + } + } + + // Now generate the report + report, err := fsquota.GetGroupReport(testMountPointQuotasEnabled) + require.NoError(t, err) + require.NotNil(t, report) + + // Check that the report for every user is correct... + for gid, expectedBytes := range expectedGroupByteUsages { + groupReport, groupReportExists := report.Infos[fmt.Sprint(gid)] + + groupReportFound := assert.True(t, groupReportExists, "Report info for GID %d is missing", gid) + groupReportNotNil := assert.NotNil(t, groupReport, "Report for GID %d is nil", gid) + + if groupReportFound && groupReportNotNil { + assert.EqualValues(t, expectedBytes, groupReport.BytesUsed, "Bytes used for GID %d is invalid. Expected %d, actual %d", gid, expectedBytes, groupReport.BytesUsed) + assert.EqualValues(t, 1+(gid-gidBase), groupReport.FilesUsed, "Files used for GID %d is invalid. Expected %d, actual %d", gid, 1+(gid-gidBase), groupReport.FilesUsed) + + // Validate that the configured quotas are correct + assert.EqualValues(t, 10*1024*1024, groupReport.Bytes.GetSoft(), "Byte soft quota for GID %d is invalid", gid) + assert.EqualValues(t, 500*1024*1024, groupReport.Bytes.GetHard(), "Byte hard quota for GID %d is invalid", gid) + assert.EqualValues(t, 1000, groupReport.Files.GetSoft(), "File soft quota for GID %d is invalid", gid) + assert.EqualValues(t, 5000, groupReport.Files.GetHard(), "File hard quota for GID %d is invalid", gid) + } - supported, err := fsquota.GroupQuotasSupported(testMountPoint) - assert.True(t, supported) - assert.NoError(t, err) + } + }) - // We expect / not to have quota support enabled - supported, err = fsquota.GroupQuotasSupported("/") - assert.False(t, supported) - assert.Error(t, err) + t.Run("QuotasDisabled", func(t *testing.T) { + report, err := fsquota.GetGroupReport(testMountPointQuotasDisabled) + assert.Error(t, err) + assert.Nil(t, report) + }) } diff --git a/util_linux_test.go b/util_linux_test.go new file mode 100644 index 0000000..be3ed86 --- /dev/null +++ b/util_linux_test.go @@ -0,0 +1,48 @@ +package fsquota + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetIDsFromUserOrGroupFile(t *testing.T) { + t.Run("FileNotFound", func(t *testing.T) { + dirName, err := ioutil.TempDir("", "fsquota-test-") + require.NoError(t, err) + defer os.RemoveAll(dirName) + + ids, err := getIDsFromUserOrGroupFile(filepath.Join(dirName, "non-existent")) + assert.Nil(t, ids) + if assert.Error(t, err) { + assert.True(t, os.IsNotExist(err)) + } + }) + + t.Run("OK", func(t *testing.T) { + dirName, err := ioutil.TempDir("", "fsquota-test-") + require.NoError(t, err) + defer os.RemoveAll(dirName) + + fileData := ` +# line without colon +too:few:parts +ignored:ignored:unparsable:ignored +ignored:ignored:1:ok1 +ignored:ignored:2:ok2 +ignored:ignored:1000:ok1000 +` + + fileName := filepath.Join(dirName, "ids") + require.NoError(t, ioutil.WriteFile(fileName, []byte(fileData), 0640)) + + ids, err := getIDsFromUserOrGroupFile(fileName) + assert.NoError(t, err) + assert.EqualValues(t, []uint32{1, 2, 1000}, ids) + + }) +}