//go:build linux

package kernel

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/metric"
	"github.com/influxdata/telegraf/testutil"
)

func TestGetProcValueInt(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_full",
		entropyStatFile: "testdata/entropy_stat_file_full",
	}

	d, err := k.getProcValueInt(k.entropyStatFile)
	require.NoError(t, err)
	require.IsType(t, int64(1), d)
}

func TestGetProcValueByte(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_full",
		entropyStatFile: "testdata/entropy_stat_file_full",
	}

	d, err := k.getProcValueBytes(k.entropyStatFile)
	require.NoError(t, err)
	require.IsType(t, []byte("test"), d)
}

func TestFullProcFile(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_full",
		entropyStatFile: "testdata/entropy_stat_file_full",
	}

	acc := testutil.Accumulator{}
	require.NoError(t, k.Gather(&acc))

	expected := []telegraf.Metric{
		metric.New(
			"kernel",
			map[string]string{},
			map[string]interface{}{
				"boot_time":        int64(1457505775),
				"context_switches": int64(2626618),
				"disk_pages_in":    int64(5741),
				"disk_pages_out":   int64(1808),
				"interrupts":       int64(1472736),
				"processes_forked": int64(10673),
				"entropy_avail":    int64(1024),
			},
			time.Unix(0, 0),
			1,
		),
	}
	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestPartialProcFile(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_partial",
		entropyStatFile: "testdata/entropy_stat_file_partial",
	}

	acc := testutil.Accumulator{}
	require.NoError(t, k.Gather(&acc))

	fields := map[string]interface{}{
		"boot_time":        int64(1457505775),
		"context_switches": int64(2626618),
		"disk_pages_in":    int64(5741),
		"disk_pages_out":   int64(1808),
		"interrupts":       int64(1472736),
		"entropy_avail":    int64(1024),
	}
	acc.AssertContainsFields(t, "kernel", fields)
}

func TestInvalidProcFile1(t *testing.T) {
	// missing btime measurement
	k := Kernel{
		statFile:        "testdata/stat_file_invalid",
		entropyStatFile: "testdata/entropy_stat_file_invalid",
	}

	acc := testutil.Accumulator{}
	err := k.Gather(&acc)
	require.Error(t, err)
	require.Contains(t, err.Error(), "invalid syntax")
}

func TestInvalidProcFile2(t *testing.T) {
	// missing second page measurement
	k := Kernel{
		statFile: "testdata/stat_file_invalid2",
	}

	acc := testutil.Accumulator{}
	err := k.Gather(&acc)
	require.Error(t, err)
	require.Contains(t, err.Error(), "does not exist")
}

func TestNoProcFile(t *testing.T) {
	k := Kernel{
		statFile: "testdata/this_file_does_not_exist",
	}

	acc := testutil.Accumulator{}
	err := k.Gather(&acc)
	require.Error(t, err)
	require.Contains(t, err.Error(), "does not exist")
}

func TestInvalidCollectOption(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_full",
		entropyStatFile: "testdata/entropy_stat_file_full",
		ConfigCollect:   []string{"invalidOption"},
	}

	acc := testutil.Accumulator{}

	require.NoError(t, k.Init())
	require.NoError(t, k.Gather(&acc))
}

func TestKsmEnabledValidKsmDirectory(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_full",
		entropyStatFile: "testdata/entropy_stat_file_full",
		ksmStatsDir:     "testdata/ksm/valid",
		ConfigCollect:   []string{"ksm"},
	}

	require.NoError(t, k.Init())

	acc := testutil.Accumulator{}
	require.NoError(t, k.Gather(&acc))

	expected := []telegraf.Metric{
		metric.New(
			"kernel",
			map[string]string{},
			map[string]interface{}{
				"boot_time":                              int64(1457505775),
				"context_switches":                       int64(2626618),
				"disk_pages_in":                          int64(5741),
				"disk_pages_out":                         int64(1808),
				"interrupts":                             int64(1472736),
				"processes_forked":                       int64(10673),
				"entropy_avail":                          int64(1024),
				"ksm_full_scans":                         int64(123),
				"ksm_max_page_sharing":                   int64(10000),
				"ksm_merge_across_nodes":                 int64(1),
				"ksm_pages_shared":                       int64(12922),
				"ksm_pages_sharing":                      int64(28384),
				"ksm_pages_to_scan":                      int64(12928),
				"ksm_pages_unshared":                     int64(92847),
				"ksm_pages_volatile":                     int64(2824171),
				"ksm_run":                                int64(1),
				"ksm_sleep_millisecs":                    int64(1000),
				"ksm_stable_node_chains":                 int64(0),
				"ksm_stable_node_chains_prune_millisecs": int64(0),
				"ksm_stable_node_dups":                   int64(0),
				"ksm_use_zero_pages":                     int64(1),
			},
			time.Unix(0, 0),
			1,
		),
	}
	testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

func TestKSMEnabledMissingFile(t *testing.T) {
	k := Kernel{
		statFile:        "/proc/stat",
		entropyStatFile: "/proc/sys/kernel/random/entropy_avail",
		ksmStatsDir:     "testdata/ksm/missing",
		ConfigCollect:   []string{"ksm"},
	}

	require.NoError(t, k.Init())

	acc := testutil.Accumulator{}
	require.ErrorContains(t, k.Gather(&acc), "does not exist")
}

func TestKSMEnabledWrongDir(t *testing.T) {
	k := Kernel{
		ksmStatsDir:   "testdata/this_file_does_not_exist",
		ConfigCollect: []string{"ksm"},
	}

	require.ErrorContains(t, k.Init(), "KSM is not enabled in this kernel")
}

func TestKSMDisabledNoKSMTags(t *testing.T) {
	k := Kernel{
		statFile:        "testdata/stat_file_full",
		entropyStatFile: "testdata/entropy_stat_file_full",
		ksmStatsDir:     "testdata/this_file_does_not_exist",
		ConfigCollect:   []string{},
	}

	acc := testutil.Accumulator{}

	require.NoError(t, k.Init())
	require.NoError(t, k.Gather(&acc))
	require.False(t, acc.HasField("kernel", "ksm_run"))
}
