// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build !darwin || !arm64 || !cgo

package fingerprint

import (
	"strconv"
	"testing"

	"github.com/hashicorp/nomad/ci"
	"github.com/hashicorp/nomad/client/config"
	"github.com/hashicorp/nomad/client/lib/cgutil"
	"github.com/hashicorp/nomad/helper/testlog"
	"github.com/hashicorp/nomad/nomad/structs"
	"github.com/shoenig/test/must"
)

func TestCPUFingerprint_Classic(t *testing.T) {
	ci.Parallel(t)

	logger := testlog.HCLogger(t)

	// create cpuset manager so we can ensure cgroup tree is correct
	mgr := cgutil.CreateCPUSetManager("", nil, logger)
	mgr.Init()

	// create the fingerprinter
	f := NewCPUFingerprint(logger)
	node := &structs.Node{Attributes: make(map[string]string)}

	request := &FingerprintRequest{Config: &config.Config{}, Node: node}
	var response FingerprintResponse

	// run the fingerprinter
	err := f.Fingerprint(request, &response)
	must.NoError(t, err)

	must.True(t, response.Detected)
	attributes := response.Attributes
	must.NotNil(t, attributes)
	must.MapContainsKey(t, attributes, "cpu.numcores")
	must.MapContainsKey(t, attributes, "cpu.modelname")
	must.MapContainsKey(t, attributes, "cpu.frequency")
	must.MapContainsKey(t, attributes, "cpu.totalcompute")
	must.Positive(t, response.Resources.CPU)
	must.Positive(t, response.NodeResources.Cpu.CpuShares)
	must.Positive(t, response.NodeResources.Cpu.SharesPerCore())
	must.SliceNotEmpty(t, response.NodeResources.Cpu.ReservableCpuCores)

	// asymetric core detection currently only works with apple silicon
	must.MapNotContainsKey(t, attributes, "cpu.numcores.power")
	must.MapNotContainsKey(t, attributes, "cpu.numcores.efficiency")
}

// TestCPUFingerprint_OverrideCompute asserts that setting cpu_total_compute in
// the client config overrides the detected CPU freq (if any).
func TestCPUFingerprint_OverrideCompute(t *testing.T) {
	ci.Parallel(t)

	f := NewCPUFingerprint(testlog.HCLogger(t))
	node := &structs.Node{
		Attributes: make(map[string]string),
	}
	cfg := &config.Config{
		ReservableCores: []uint16{0, 1, 2},
	}
	var originalCPU int

	{
		request := &FingerprintRequest{Config: cfg, Node: node}
		var response FingerprintResponse
		err := f.Fingerprint(request, &response)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		if !response.Detected {
			t.Fatalf("expected response to be applicable")
		}

		if attr := response.Attributes["cpu.reservablecores"]; attr != "3" {
			t.Fatalf("expected cpu.reservablecores == 3 but found %s", attr)
		}

		if response.Resources.CPU == 0 {
			t.Fatalf("expected fingerprint of cpu of but found 0")
		}

		originalCPU = response.Resources.CPU
	}

	{
		// Override it with a setting
		cfg.CpuCompute = originalCPU + 123

		// Make sure the Fingerprinter applies the override to the node resources
		request := &FingerprintRequest{Config: cfg, Node: node}
		var response FingerprintResponse
		err := f.Fingerprint(request, &response)
		if err != nil {
			t.Fatalf("err: %v", err)
		}

		// COMPAT(0.10): Remove in 0.10
		if response.Resources.CPU != cfg.CpuCompute {
			t.Fatalf("expected override cpu of %d but found %d", cfg.CpuCompute, response.Resources.CPU)
		}
		if response.NodeResources.Cpu.CpuShares != int64(cfg.CpuCompute) {
			t.Fatalf("expected override cpu of %d but found %d", cfg.CpuCompute, response.NodeResources.Cpu.CpuShares)
		}
		if response.Attributes["cpu.totalcompute"] != strconv.Itoa(cfg.CpuCompute) {
			t.Fatalf("expected override cpu.totalcompute of %d but found %s", cfg.CpuCompute, response.Attributes["cpu.totalcompute"])
		}

		if attr := response.Attributes["cpu.reservablecores"]; attr != "3" {
			t.Fatalf("expected cpu.reservablecores == 3 but found %s", attr)
		}
	}
}
