package fs

import (
	"maps"
	"strconv"
	"testing"

	"github.com/opencontainers/cgroups"
	"github.com/opencontainers/cgroups/fscommon"
)

const (
	sectorsRecursiveContents      = `8:0 1024`
	sectorsRecursiveContentsBFQ   = `8:0 2048`
	serviceBytesRecursiveContents = `8:0 Read 100
8:0 Write 200
8:0 Sync 300
8:0 Async 500
8:0 Total 500
Total 500`

	serviceBytesRecursiveContentsBFQ = `8:0 Read 1100
8:0 Write 1200
8:0 Sync 1300
8:0 Async 1500
8:0 Total 1500
Total 1500`
	servicedRecursiveContents = `8:0 Read 10
8:0 Write 40
8:0 Sync 20
8:0 Async 30
8:0 Total 50
Total 50`
	servicedRecursiveContentsBFQ = `8:0 Read 11
8:0 Write 41
8:0 Sync 21
8:0 Async 31
8:0 Total 51
Total 51`
	queuedRecursiveContents = `8:0 Read 1
8:0 Write 4
8:0 Sync 2
8:0 Async 3
8:0 Total 5
Total 5`
	queuedRecursiveContentsBFQ = `8:0 Read 2
8:0 Write 3
8:0 Sync 4
8:0 Async 5
8:0 Total 6
Total 6`
	serviceTimeRecursiveContents = `8:0 Read 173959
8:0 Write 0
8:0 Sync 0
8:0 Async 173959
8:0 Total 17395
Total 17395`
	serviceTimeRecursiveContentsBFQ = `8:0 Read 173959
8:0 Write 0
8:0 Sync 0
8:0 Async 173
8:0 Total 174
Total 174`
	waitTimeRecursiveContents = `8:0 Read 15571
8:0 Write 0
8:0 Sync 0
8:0 Async 15571
8:0 Total 15571`
	waitTimeRecursiveContentsBFQ = `8:0 Read 1557
8:0 Write 0
8:0 Sync 0
8:0 Async 1557
8:0 Total 1557`
	mergedRecursiveContents = `8:0 Read 5
8:0 Write 10
8:0 Sync 0
8:0 Async 0
8:0 Total 15
Total 15`
	mergedRecursiveContentsBFQ = `8:0 Read 51
8:0 Write 101
8:0 Sync 0
8:0 Async 0
8:0 Total 151
Total 151`
	timeRecursiveContents    = `8:0 8`
	timeRecursiveContentsBFQ = `8:0 16`
	throttleServiceBytes     = `8:0 Read 11030528
8:0 Write 23
8:0 Sync 42
8:0 Async 11030528
8:0 Total 11030528
252:0 Read 11030528
252:0 Write 23
252:0 Sync 42
252:0 Async 11030528
252:0 Total 11030528
Total 22061056`
	throttleServiceBytesRecursive = `8:0 Read 110305281
8:0 Write 231
8:0 Sync 421
8:0 Async 110305281
8:0 Total 110305281
252:0 Read 110305281
252:0 Write 231
252:0 Sync 421
252:0 Async 110305281
252:0 Total 110305281
Total 220610561`
	throttleServiced = `8:0 Read 164
8:0 Write 23
8:0 Sync 42
8:0 Async 164
8:0 Total 164
252:0 Read 164
252:0 Write 23
252:0 Sync 42
252:0 Async 164
252:0 Total 164
Total 328`
	throttleServicedRecursive = `8:0 Read 1641
8:0 Write 231
8:0 Sync 421
8:0 Async 1641
8:0 Total 1641
252:0 Read 1641
252:0 Write 231
252:0 Sync 421
252:0 Async 1641
252:0 Total 1641
Total 3281`
)

var blkioBFQDebugStatsTestFiles = map[string]string{
	"blkio.bfq.io_service_bytes_recursive": serviceBytesRecursiveContentsBFQ,
	"blkio.bfq.io_serviced_recursive":      servicedRecursiveContentsBFQ,
	"blkio.bfq.io_queued_recursive":        queuedRecursiveContentsBFQ,
	"blkio.bfq.io_service_time_recursive":  serviceTimeRecursiveContentsBFQ,
	"blkio.bfq.io_wait_time_recursive":     waitTimeRecursiveContentsBFQ,
	"blkio.bfq.io_merged_recursive":        mergedRecursiveContentsBFQ,
	"blkio.bfq.time_recursive":             timeRecursiveContentsBFQ,
	"blkio.bfq.sectors_recursive":          sectorsRecursiveContentsBFQ,
}

var blkioBFQStatsTestFiles = map[string]string{
	"blkio.bfq.io_service_bytes_recursive": serviceBytesRecursiveContentsBFQ,
	"blkio.bfq.io_serviced_recursive":      servicedRecursiveContentsBFQ,
}

var blkioCFQStatsTestFiles = map[string]string{
	"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
	"blkio.io_serviced_recursive":      servicedRecursiveContents,
	"blkio.io_queued_recursive":        queuedRecursiveContents,
	"blkio.io_service_time_recursive":  serviceTimeRecursiveContents,
	"blkio.io_wait_time_recursive":     waitTimeRecursiveContents,
	"blkio.io_merged_recursive":        mergedRecursiveContents,
	"blkio.time_recursive":             timeRecursiveContents,
	"blkio.sectors_recursive":          sectorsRecursiveContents,
}

type blkioStatFailureTestCase struct {
	desc     string
	filename string
}

func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { //nolint:unparam
	*blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op})
}

func TestBlkioSetWeight(t *testing.T) {
	const (
		weightBefore = 100
		weightAfter  = 200
	)

	for _, legacyIOScheduler := range []bool{false, true} {
		// Populate cgroup
		path := tempDir(t, "blkio")
		weightFilename := "blkio.bfq.weight"
		if legacyIOScheduler {
			weightFilename = "blkio.weight"
		}
		writeFileContents(t, path, map[string]string{
			weightFilename: strconv.Itoa(weightBefore),
		})
		// Apply new configuration
		r := &cgroups.Resources{
			BlkioWeight: weightAfter,
		}
		blkio := &BlkioGroup{}
		if err := blkio.Set(path, r); err != nil {
			t.Fatal(err)
		}
		// Verify results
		if weightFilename != blkio.weightFilename {
			t.Fatalf("weight filename detection failed: expected %q, detected %q", weightFilename, blkio.weightFilename)
		}
		value, err := fscommon.GetCgroupParamUint(path, weightFilename)
		if err != nil {
			t.Fatal(err)
		}
		if value != weightAfter {
			t.Fatalf("Got the wrong value, set %s failed.", weightFilename)
		}
	}
}

func TestBlkioSetWeightDevice(t *testing.T) {
	const (
		weightDeviceBefore = "8:0 400"
	)

	for _, legacyIOScheduler := range []bool{false, true} {
		// Populate cgroup
		path := tempDir(t, "blkio")
		weightFilename := "blkio.bfq.weight"
		weightDeviceFilename := "blkio.bfq.weight_device"
		if legacyIOScheduler {
			weightFilename = "blkio.weight"
			weightDeviceFilename = "blkio.weight_device"
		}
		writeFileContents(t, path, map[string]string{
			weightFilename:       "",
			weightDeviceFilename: weightDeviceBefore,
		})
		// Apply new configuration
		wd := cgroups.NewWeightDevice(8, 0, 500, 0)
		weightDeviceAfter := wd.WeightString()
		r := &cgroups.Resources{
			BlkioWeightDevice: []*cgroups.WeightDevice{wd},
		}
		blkio := &BlkioGroup{}
		if err := blkio.Set(path, r); err != nil {
			t.Fatal(err)
		}
		// Verify results
		if weightDeviceFilename != blkio.weightDeviceFilename {
			t.Fatalf("weight_device filename detection failed: expected %q, detected %q", weightDeviceFilename, blkio.weightDeviceFilename)
		}
		value, err := fscommon.GetCgroupParamString(path, weightDeviceFilename)
		if err != nil {
			t.Fatal(err)
		}
		if value != weightDeviceAfter {
			t.Fatalf("Got the wrong value, set %s failed.", weightDeviceFilename)
		}
	}
}

// regression #274
func TestBlkioSetMultipleWeightDevice(t *testing.T) {
	path := tempDir(t, "blkio")

	const (
		weightDeviceBefore = "8:0 400"
	)

	wd1 := cgroups.NewWeightDevice(8, 0, 500, 0)
	wd2 := cgroups.NewWeightDevice(8, 16, 500, 0)
	// we cannot actually set and check both because normal os.WriteFile
	// when writing to cgroup file will overwrite the whole file content instead
	// of updating it as the kernel is doing. Just check the second device
	// is present will suffice for the test to ensure multiple writes are done.
	weightDeviceAfter := wd2.WeightString()

	blkio := &BlkioGroup{}
	blkio.detectWeightFilenames(path)
	if blkio.weightDeviceFilename != "blkio.bfq.weight_device" {
		t.Fatalf("when blkio controller is unavailable, expected to use \"blkio.bfq.weight_device\", tried to use %q", blkio.weightDeviceFilename)
	}
	writeFileContents(t, path, map[string]string{
		blkio.weightDeviceFilename: weightDeviceBefore,
	})

	r := &cgroups.Resources{
		BlkioWeightDevice: []*cgroups.WeightDevice{wd1, wd2},
	}
	if err := blkio.Set(path, r); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(path, blkio.weightDeviceFilename)
	if err != nil {
		t.Fatal(err)
	}
	if value != weightDeviceAfter {
		t.Fatalf("Got the wrong value, set %s failed.", blkio.weightDeviceFilename)
	}
}

func TestBlkioBFQDebugStats(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, blkioBFQDebugStatsTestFiles)
	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	expectedStats := cgroups.BlkioStats{}
	appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 2048, "")

	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1100, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1200, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1300, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1500, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1500, "Total")

	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 11, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 41, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 21, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 31, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 51, "Total")

	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Read")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Write")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Sync")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Async")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 6, "Total")

	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 174, "Total")

	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 1557, "Read")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Write")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 1557, "Async")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 1557, "Total")

	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 51, "Read")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 101, "Write")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Async")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 151, "Total")

	appendBlkioStatEntry(&expectedStats.IoTimeRecursive, 8, 0, 16, "")

	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
}

func TestBlkioMultipleStatsFiles(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, blkioBFQDebugStatsTestFiles)
	writeFileContents(t, path, blkioCFQStatsTestFiles)
	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	expectedStats := cgroups.BlkioStats{}
	appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 2048, "")

	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1100, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1200, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1300, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1500, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1500, "Total")

	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 11, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 41, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 21, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 31, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 51, "Total")

	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Read")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Write")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Sync")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Async")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 6, "Total")

	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 174, "Total")

	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 1557, "Read")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Write")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 1557, "Async")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 1557, "Total")

	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 51, "Read")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 101, "Write")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Async")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 151, "Total")

	appendBlkioStatEntry(&expectedStats.IoTimeRecursive, 8, 0, 16, "")

	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
}

func TestBlkioBFQStats(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, blkioBFQStatsTestFiles)
	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	expectedStats := cgroups.BlkioStats{}

	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1100, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1200, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1300, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1500, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 1500, "Total")

	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 11, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 41, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 21, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 31, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 51, "Total")

	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
}

func TestBlkioStatsNoFilesBFQDebug(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}
	testCases := []blkioStatFailureTestCase{
		{
			desc:     "missing blkio.bfq.io_service_bytes_recursive file",
			filename: "blkio.bfq.io_service_bytes_recursive",
		},
		{
			desc:     "missing blkio.bfq.io_serviced_recursive file",
			filename: "blkio.bfq.io_serviced_recursive",
		},
		{
			desc:     "missing blkio.bfq.io_queued_recursive file",
			filename: "blkio.bfq.io_queued_recursive",
		},
		{
			desc:     "missing blkio.bfq.sectors_recursive file",
			filename: "blkio.bfq.sectors_recursive",
		},
		{
			desc:     "missing blkio.bfq.io_service_time_recursive file",
			filename: "blkio.bfq.io_service_time_recursive",
		},
		{
			desc:     "missing blkio.bfq.io_wait_time_recursive file",
			filename: "blkio.bfq.io_wait_time_recursive",
		},
		{
			desc:     "missing blkio.bfq.io_merged_recursive file",
			filename: "blkio.bfq.io_merged_recursive",
		},
		{
			desc:     "missing blkio.bfq.time_recursive file",
			filename: "blkio.bfq.time_recursive",
		},
	}

	for _, testCase := range testCases {
		path := tempDir(t, "cpuset")

		tempBlkioTestFiles := maps.Clone(blkioBFQDebugStatsTestFiles)
		delete(tempBlkioTestFiles, testCase.filename)

		writeFileContents(t, path, tempBlkioTestFiles)
		cpuset := &CpusetGroup{}
		actualStats := *cgroups.NewStats()
		err := cpuset.GetStats(path, &actualStats)
		if err != nil {
			t.Errorf("%s: want no error, got: %+v", testCase.desc, err)
		}
	}
}

func TestBlkioCFQStats(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, blkioCFQStatsTestFiles)

	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	// Verify expected stats.
	expectedStats := cgroups.BlkioStats{}
	appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "")

	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total")

	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total")

	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async")
	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total")

	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 173959, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceTimeRecursive, 8, 0, 17395, "Total")

	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Read")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Write")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Async")
	appendBlkioStatEntry(&expectedStats.IoWaitTimeRecursive, 8, 0, 15571, "Total")

	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 5, "Read")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 10, "Write")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Sync")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 0, "Async")
	appendBlkioStatEntry(&expectedStats.IoMergedRecursive, 8, 0, 15, "Total")

	appendBlkioStatEntry(&expectedStats.IoTimeRecursive, 8, 0, 8, "")

	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
}

func TestBlkioStatsNoFilesCFQ(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping test in short mode.")
	}
	testCases := []blkioStatFailureTestCase{
		{
			desc:     "missing blkio.io_service_bytes_recursive file",
			filename: "blkio.io_service_bytes_recursive",
		},
		{
			desc:     "missing blkio.io_serviced_recursive file",
			filename: "blkio.io_serviced_recursive",
		},
		{
			desc:     "missing blkio.io_queued_recursive file",
			filename: "blkio.io_queued_recursive",
		},
		{
			desc:     "missing blkio.sectors_recursive file",
			filename: "blkio.sectors_recursive",
		},
		{
			desc:     "missing blkio.io_service_time_recursive file",
			filename: "blkio.io_service_time_recursive",
		},
		{
			desc:     "missing blkio.io_wait_time_recursive file",
			filename: "blkio.io_wait_time_recursive",
		},
		{
			desc:     "missing blkio.io_merged_recursive file",
			filename: "blkio.io_merged_recursive",
		},
		{
			desc:     "missing blkio.time_recursive file",
			filename: "blkio.time_recursive",
		},
	}

	for _, testCase := range testCases {
		path := tempDir(t, "cpuset")

		tempBlkioTestFiles := maps.Clone(blkioCFQStatsTestFiles)
		delete(tempBlkioTestFiles, testCase.filename)

		writeFileContents(t, path, tempBlkioTestFiles)
		cpuset := &CpusetGroup{}
		actualStats := *cgroups.NewStats()
		err := cpuset.GetStats(path, &actualStats)
		if err != nil {
			t.Errorf("%s: want no error, got %+v", testCase.desc, err)
		}
	}
}

func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, map[string]string{
		"blkio.io_service_bytes_recursive": "8:0 Read 100 100",
		"blkio.io_serviced_recursive":      servicedRecursiveContents,
		"blkio.io_queued_recursive":        queuedRecursiveContents,
		"blkio.sectors_recursive":          sectorsRecursiveContents,
		"blkio.io_service_time_recursive":  serviceTimeRecursiveContents,
		"blkio.io_wait_time_recursive":     waitTimeRecursiveContents,
		"blkio.io_merged_recursive":        mergedRecursiveContents,
		"blkio.time_recursive":             timeRecursiveContents,
	})

	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err == nil {
		t.Fatal("Expected to fail, but did not")
	}
}

func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, map[string]string{
		"blkio.io_service_bytes_recursive": "8:0 Read Write",
		"blkio.io_serviced_recursive":      servicedRecursiveContents,
		"blkio.io_queued_recursive":        queuedRecursiveContents,
		"blkio.sectors_recursive":          sectorsRecursiveContents,
		"blkio.io_service_time_recursive":  serviceTimeRecursiveContents,
		"blkio.io_wait_time_recursive":     waitTimeRecursiveContents,
		"blkio.io_merged_recursive":        mergedRecursiveContents,
		"blkio.time_recursive":             timeRecursiveContents,
	})

	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err == nil {
		t.Fatal("Expected to fail, but did not")
	}
}

func TestThrottleRecursiveBlkioStats(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, map[string]string{
		"blkio.io_service_bytes_recursive":          "",
		"blkio.io_serviced_recursive":               "",
		"blkio.io_queued_recursive":                 "",
		"blkio.sectors_recursive":                   "",
		"blkio.io_service_time_recursive":           "",
		"blkio.io_wait_time_recursive":              "",
		"blkio.io_merged_recursive":                 "",
		"blkio.time_recursive":                      "",
		"blkio.throttle.io_service_bytes_recursive": throttleServiceBytesRecursive,
		"blkio.throttle.io_serviced_recursive":      throttleServicedRecursive,
	})

	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	// Verify expected stats.
	expectedStats := cgroups.BlkioStats{}

	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 110305281, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 231, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 421, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 110305281, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 110305281, "Total")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 110305281, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 231, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 421, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 110305281, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 110305281, "Total")

	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 1641, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 231, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 421, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 1641, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 1641, "Total")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 1641, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 231, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 421, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 1641, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 1641, "Total")

	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
}

func TestThrottleBlkioStats(t *testing.T) {
	path := tempDir(t, "blkio")
	writeFileContents(t, path, map[string]string{
		"blkio.io_service_bytes_recursive": "",
		"blkio.io_serviced_recursive":      "",
		"blkio.io_queued_recursive":        "",
		"blkio.sectors_recursive":          "",
		"blkio.io_service_time_recursive":  "",
		"blkio.io_wait_time_recursive":     "",
		"blkio.io_merged_recursive":        "",
		"blkio.time_recursive":             "",
		"blkio.throttle.io_service_bytes":  throttleServiceBytes,
		"blkio.throttle.io_serviced":       throttleServiced,
	})

	blkio := &BlkioGroup{}
	actualStats := *cgroups.NewStats()
	err := blkio.GetStats(path, &actualStats)
	if err != nil {
		t.Fatal(err)
	}

	// Verify expected stats.
	expectedStats := cgroups.BlkioStats{}

	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 23, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 42, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 11030528, "Total")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Read")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 23, "Write")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 42, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Async")
	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 252, 0, 11030528, "Total")

	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 23, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 42, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 164, "Total")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Read")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 23, "Write")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 42, "Sync")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Async")
	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 252, 0, 164, "Total")

	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
}

func TestBlkioSetThrottleReadBpsDevice(t *testing.T) {
	path := tempDir(t, "blkio")

	const (
		throttleBefore = `8:0 1024`
	)

	td := cgroups.NewThrottleDevice(8, 0, 2048)
	throttleAfter := td.String()

	writeFileContents(t, path, map[string]string{
		"blkio.throttle.read_bps_device": throttleBefore,
	})

	r := &cgroups.Resources{
		BlkioThrottleReadBpsDevice: []*cgroups.ThrottleDevice{td},
	}
	blkio := &BlkioGroup{}
	if err := blkio.Set(path, r); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(path, "blkio.throttle.read_bps_device")
	if err != nil {
		t.Fatal(err)
	}
	if value != throttleAfter {
		t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.")
	}
}

func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) {
	path := tempDir(t, "blkio")

	const (
		throttleBefore = `8:0 1024`
	)

	td := cgroups.NewThrottleDevice(8, 0, 2048)
	throttleAfter := td.String()

	writeFileContents(t, path, map[string]string{
		"blkio.throttle.write_bps_device": throttleBefore,
	})

	r := &cgroups.Resources{
		BlkioThrottleWriteBpsDevice: []*cgroups.ThrottleDevice{td},
	}
	blkio := &BlkioGroup{}
	if err := blkio.Set(path, r); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(path, "blkio.throttle.write_bps_device")
	if err != nil {
		t.Fatal(err)
	}
	if value != throttleAfter {
		t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.")
	}
}

func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) {
	path := tempDir(t, "blkio")

	const (
		throttleBefore = `8:0 1024`
	)

	td := cgroups.NewThrottleDevice(8, 0, 2048)
	throttleAfter := td.String()

	writeFileContents(t, path, map[string]string{
		"blkio.throttle.read_iops_device": throttleBefore,
	})

	r := &cgroups.Resources{
		BlkioThrottleReadIOPSDevice: []*cgroups.ThrottleDevice{td},
	}
	blkio := &BlkioGroup{}
	if err := blkio.Set(path, r); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(path, "blkio.throttle.read_iops_device")
	if err != nil {
		t.Fatal(err)
	}
	if value != throttleAfter {
		t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.")
	}
}

func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) {
	path := tempDir(t, "blkio")

	const (
		throttleBefore = `8:0 1024`
	)

	td := cgroups.NewThrottleDevice(8, 0, 2048)
	throttleAfter := td.String()

	writeFileContents(t, path, map[string]string{
		"blkio.throttle.write_iops_device": throttleBefore,
	})

	r := &cgroups.Resources{
		BlkioThrottleWriteIOPSDevice: []*cgroups.ThrottleDevice{td},
	}
	blkio := &BlkioGroup{}
	if err := blkio.Set(path, r); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(path, "blkio.throttle.write_iops_device")
	if err != nil {
		t.Fatal(err)
	}
	if value != throttleAfter {
		t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.")
	}
}
