// +build linux

package fs

import (
	"reflect"
	"testing"

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

const (
	cpus                  = "0-2,7,12-14\n"
	cpuExclusive          = "1\n"
	mems                  = "1-4,6,9\n"
	memHardwall           = "0\n"
	memExclusive          = "0\n"
	memoryMigrate         = "1\n"
	memorySpreadPage      = "0\n"
	memorySpeadSlab       = "1\n"
	memoryPressure        = "34377\n"
	schedLoadBalance      = "1\n"
	schedRelaxDomainLevel = "-1\n"
)

var cpusetTestFiles = map[string]string{
	"cpuset.cpus":                     cpus,
	"cpuset.cpu_exclusive":            cpuExclusive,
	"cpuset.mems":                     mems,
	"cpuset.mem_hardwall":             memHardwall,
	"cpuset.mem_exclusive":            memExclusive,
	"cpuset.memory_migrate":           memoryMigrate,
	"cpuset.memory_spread_page":       memorySpreadPage,
	"cpuset.memory_spread_slab":       memorySpeadSlab,
	"cpuset.memory_pressure":          memoryPressure,
	"cpuset.sched_load_balance":       schedLoadBalance,
	"cpuset.sched_relax_domain_level": schedRelaxDomainLevel,
}

func TestCPUSetSetCpus(t *testing.T) {
	helper := NewCgroupTestUtil("cpuset", t)
	defer helper.cleanup()

	const (
		cpusBefore = "0"
		cpusAfter  = "1-3"
	)

	helper.writeFileContents(map[string]string{
		"cpuset.cpus": cpusBefore,
	})

	helper.CgroupData.config.Resources.CpusetCpus = cpusAfter
	cpuset := &CpusetGroup{}
	if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "cpuset.cpus")
	if err != nil {
		t.Fatalf("Failed to parse cpuset.cpus - %s", err)
	}

	if value != cpusAfter {
		t.Fatal("Got the wrong value, set cpuset.cpus failed.")
	}
}

func TestCPUSetSetMems(t *testing.T) {
	helper := NewCgroupTestUtil("cpuset", t)
	defer helper.cleanup()

	const (
		memsBefore = "0"
		memsAfter  = "1"
	)

	helper.writeFileContents(map[string]string{
		"cpuset.mems": memsBefore,
	})

	helper.CgroupData.config.Resources.CpusetMems = memsAfter
	cpuset := &CpusetGroup{}
	if err := cpuset.Set(helper.CgroupPath, helper.CgroupData.config.Resources); err != nil {
		t.Fatal(err)
	}

	value, err := fscommon.GetCgroupParamString(helper.CgroupPath, "cpuset.mems")
	if err != nil {
		t.Fatalf("Failed to parse cpuset.mems - %s", err)
	}

	if value != memsAfter {
		t.Fatal("Got the wrong value, set cpuset.mems failed.")
	}
}

func TestCPUSetStatsCorrect(t *testing.T) {
	helper := NewCgroupTestUtil("cpuset", t)
	defer helper.cleanup()
	helper.writeFileContents(cpusetTestFiles)

	cpuset := &CpusetGroup{}
	actualStats := *cgroups.NewStats()
	err := cpuset.GetStats(helper.CgroupPath, &actualStats)
	if err != nil {
		t.Fatal(err)
	}
	expectedStats := cgroups.CPUSetStats{
		CPUs:                  []uint16{0, 1, 2, 7, 12, 13, 14},
		CPUExclusive:          1,
		Mems:                  []uint16{1, 2, 3, 4, 6, 9},
		MemoryMigrate:         1,
		MemHardwall:           0,
		MemExclusive:          0,
		MemorySpreadPage:      0,
		MemorySpreadSlab:      1,
		MemoryPressure:        34377,
		SchedLoadBalance:      1,
		SchedRelaxDomainLevel: -1,
	}
	if !reflect.DeepEqual(expectedStats, actualStats.CPUSetStats) {
		t.Fatalf("Expected Cpuset stats usage %#v but found %#v",
			expectedStats, actualStats.CPUSetStats)
	}
}

func TestCPUSetStatsMissingFiles(t *testing.T) {
	for _, testCase := range []struct {
		desc               string
		filename, contents string
		removeFile         bool
	}{
		{
			desc:       "empty cpus file",
			filename:   "cpuset.cpus",
			contents:   "",
			removeFile: false,
		},
		{
			desc:       "empty mems file",
			filename:   "cpuset.mems",
			contents:   "",
			removeFile: false,
		},
		{
			desc:       "corrupted cpus file",
			filename:   "cpuset.cpus",
			contents:   "0-3,*4^2",
			removeFile: false,
		},
		{
			desc:       "corrupted mems file",
			filename:   "cpuset.mems",
			contents:   "0,1,2-5,8-7",
			removeFile: false,
		},
		{
			desc:       "missing cpu_exclusive file",
			filename:   "cpuset.cpu_exclusive",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing memory_migrate file",
			filename:   "cpuset.memory_migrate",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing mem_hardwall file",
			filename:   "cpuset.mem_hardwall",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing mem_exclusive file",
			filename:   "cpuset.mem_exclusive",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing memory_spread_page file",
			filename:   "cpuset.memory_spread_page",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing memory_spread_slab file",
			filename:   "cpuset.memory_spread_slab",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing memory_pressure file",
			filename:   "cpuset.memory_pressure",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing sched_load_balance file",
			filename:   "cpuset.sched_load_balance",
			contents:   "",
			removeFile: true,
		},
		{
			desc:       "missing sched_relax_domain_level file",
			filename:   "cpuset.sched_relax_domain_level",
			contents:   "",
			removeFile: true,
		},
	} {
		t.Run(testCase.desc, func(t *testing.T) {
			helper := NewCgroupTestUtil("cpuset", t)
			defer helper.cleanup()

			tempCpusetTestFiles := map[string]string{}
			for i, v := range cpusetTestFiles {
				tempCpusetTestFiles[i] = v
			}

			if testCase.removeFile {
				delete(tempCpusetTestFiles, testCase.filename)
				helper.writeFileContents(tempCpusetTestFiles)
				cpuset := &CpusetGroup{}
				actualStats := *cgroups.NewStats()
				err := cpuset.GetStats(helper.CgroupPath, &actualStats)
				if err != nil {
					t.Errorf("failed unexpectedly: %q", err)
				}
			} else {
				tempCpusetTestFiles[testCase.filename] = testCase.contents
				helper.writeFileContents(tempCpusetTestFiles)
				cpuset := &CpusetGroup{}
				actualStats := *cgroups.NewStats()
				err := cpuset.GetStats(helper.CgroupPath, &actualStats)

				if err == nil {
					t.Error("failed to return expected error")
				}
			}
		})
	}
}
