mirror of
https://github.com/henrygd/beszel.git
synced 2026-03-21 21:26:16 +01:00
266 lines
7.7 KiB
Go
266 lines
7.7 KiB
Go
//go:build linux
|
|
|
|
package agent
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/henrygd/beszel/agent/utils"
|
|
"github.com/henrygd/beszel/internal/entities/system"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestNormalizeHexID(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want string
|
|
}{
|
|
{"0x1002", "1002"},
|
|
{"C2", "c2"},
|
|
{" 15BF ", "15bf"},
|
|
{"0x15bf", "15bf"},
|
|
{"", ""},
|
|
}
|
|
for _, tt := range tests {
|
|
subName := tt.in
|
|
if subName == "" {
|
|
subName = "empty_string"
|
|
}
|
|
t.Run(subName, func(t *testing.T) {
|
|
got := normalizeHexID(tt.in)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCacheKeyForAmdgpu(t *testing.T) {
|
|
tests := []struct {
|
|
deviceID string
|
|
revisionID string
|
|
want string
|
|
}{
|
|
{"1114", "c2", "1114:c2"},
|
|
{"15bf", "", "15bf"},
|
|
{"1506", "c1", "1506:c1"},
|
|
}
|
|
for _, tt := range tests {
|
|
got := cacheKeyForAmdgpu(tt.deviceID, tt.revisionID)
|
|
assert.Equal(t, tt.want, got)
|
|
}
|
|
}
|
|
|
|
func TestReadSysfsFloat(t *testing.T) {
|
|
dir := t.TempDir()
|
|
|
|
validPath := filepath.Join(dir, "val")
|
|
require.NoError(t, os.WriteFile(validPath, []byte(" 42.5 \n"), 0o644))
|
|
got, err := readSysfsFloat(validPath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 42.5, got)
|
|
|
|
// Integer and scientific
|
|
sciPath := filepath.Join(dir, "sci")
|
|
require.NoError(t, os.WriteFile(sciPath, []byte("1e2"), 0o644))
|
|
got, err = readSysfsFloat(sciPath)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 100.0, got)
|
|
|
|
// Missing file
|
|
_, err = readSysfsFloat(filepath.Join(dir, "missing"))
|
|
require.Error(t, err)
|
|
|
|
// Invalid content
|
|
badPath := filepath.Join(dir, "bad")
|
|
require.NoError(t, os.WriteFile(badPath, []byte("not a number"), 0o644))
|
|
_, err = readSysfsFloat(badPath)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestIsAmdGpu(t *testing.T) {
|
|
dir := t.TempDir()
|
|
deviceDir := filepath.Join(dir, "device")
|
|
require.NoError(t, os.MkdirAll(deviceDir, 0o755))
|
|
|
|
// AMD vendor 0x1002 -> true
|
|
require.NoError(t, os.WriteFile(filepath.Join(deviceDir, "vendor"), []byte("0x1002\n"), 0o644))
|
|
assert.True(t, isAmdGpu(dir), "vendor 0x1002 should be AMD")
|
|
|
|
// Non-AMD vendor -> false
|
|
require.NoError(t, os.WriteFile(filepath.Join(deviceDir, "vendor"), []byte("0x10de\n"), 0o644))
|
|
assert.False(t, isAmdGpu(dir), "vendor 0x10de should not be AMD")
|
|
|
|
// Missing vendor file -> false
|
|
require.NoError(t, os.Remove(filepath.Join(deviceDir, "vendor")))
|
|
assert.False(t, isAmdGpu(dir), "missing vendor file should be false")
|
|
}
|
|
|
|
func TestAmdgpuNameCacheRoundTrip(t *testing.T) {
|
|
// Cache a name and retrieve it (unique key to avoid affecting other tests)
|
|
deviceID, revisionID := "cachedev99", "00"
|
|
cacheAmdgpuName(deviceID, revisionID, "AMD Test GPU 99 Graphics", true)
|
|
|
|
name, found, done := getCachedAmdgpuName(deviceID, revisionID)
|
|
assert.True(t, found)
|
|
assert.True(t, done)
|
|
assert.Equal(t, "AMD Test GPU 99", name)
|
|
|
|
// Device-only key also stored
|
|
name2, found2, _ := getCachedAmdgpuName(deviceID, "")
|
|
assert.True(t, found2)
|
|
assert.Equal(t, "AMD Test GPU 99", name2)
|
|
|
|
// Cache a miss
|
|
cacheMissingAmdgpuName("missedev99", "ab")
|
|
_, found3, done3 := getCachedAmdgpuName("missedev99", "ab")
|
|
assert.False(t, found3)
|
|
assert.True(t, done3, "done should be true so caller skips file lookup")
|
|
}
|
|
|
|
func TestUpdateAmdGpuDataWithFakeSysfs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
writeGTT bool
|
|
wantMemoryUsed float64
|
|
wantMemoryTotal float64
|
|
}{
|
|
{
|
|
name: "sums vram and gtt when gtt is present",
|
|
writeGTT: true,
|
|
wantMemoryUsed: utils.BytesToMegabytes(1073741824 + 536870912),
|
|
wantMemoryTotal: utils.BytesToMegabytes(2147483648 + 4294967296),
|
|
},
|
|
{
|
|
name: "falls back to vram when gtt is missing",
|
|
writeGTT: false,
|
|
wantMemoryUsed: utils.BytesToMegabytes(1073741824),
|
|
wantMemoryTotal: utils.BytesToMegabytes(2147483648),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
dir := t.TempDir()
|
|
cardPath := filepath.Join(dir, "card0")
|
|
devicePath := filepath.Join(cardPath, "device")
|
|
hwmonPath := filepath.Join(devicePath, "hwmon", "hwmon0")
|
|
require.NoError(t, os.MkdirAll(hwmonPath, 0o755))
|
|
|
|
write := func(name, content string) {
|
|
require.NoError(t, os.WriteFile(filepath.Join(devicePath, name), []byte(content), 0o644))
|
|
}
|
|
write("vendor", "0x1002")
|
|
write("device", "0x1506")
|
|
write("revision", "0xc1")
|
|
write("gpu_busy_percent", "25")
|
|
write("mem_info_vram_used", "1073741824")
|
|
write("mem_info_vram_total", "2147483648")
|
|
if tt.writeGTT {
|
|
write("mem_info_gtt_used", "536870912")
|
|
write("mem_info_gtt_total", "4294967296")
|
|
}
|
|
require.NoError(t, os.WriteFile(filepath.Join(hwmonPath, "temp1_input"), []byte("45000"), 0o644))
|
|
require.NoError(t, os.WriteFile(filepath.Join(hwmonPath, "power1_input"), []byte("20000000"), 0o644))
|
|
|
|
// Pre-cache name so getAmdGpuName returns a known value (it uses system amdgpu.ids path)
|
|
cacheAmdgpuName("1506", "c1", "AMD Radeon 610M Graphics", true)
|
|
|
|
gm := &GPUManager{GpuDataMap: make(map[string]*system.GPUData)}
|
|
ok := gm.updateAmdGpuData(cardPath)
|
|
require.True(t, ok)
|
|
|
|
gpu, ok := gm.GpuDataMap["card0"]
|
|
require.True(t, ok)
|
|
assert.Equal(t, "AMD Radeon 610M", gpu.Name)
|
|
assert.Equal(t, 25.0, gpu.Usage)
|
|
assert.Equal(t, tt.wantMemoryUsed, gpu.MemoryUsed)
|
|
assert.Equal(t, tt.wantMemoryTotal, gpu.MemoryTotal)
|
|
assert.Equal(t, 45.0, gpu.Temperature)
|
|
assert.Equal(t, 20.0, gpu.Power)
|
|
assert.Equal(t, 1.0, gpu.Count)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLookupAmdgpuNameInFile(t *testing.T) {
|
|
idsPath := filepath.Join("test-data", "amdgpu.ids")
|
|
|
|
tests := []struct {
|
|
name string
|
|
deviceID string
|
|
revisionID string
|
|
wantName string
|
|
wantExact bool
|
|
wantFound bool
|
|
}{
|
|
{
|
|
name: "exact device and revision match",
|
|
deviceID: "1114",
|
|
revisionID: "c2",
|
|
wantName: "AMD Radeon 860M Graphics",
|
|
wantExact: true,
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "exact match 15BF revision 01 returns 760M",
|
|
deviceID: "15bf",
|
|
revisionID: "01",
|
|
wantName: "AMD Radeon 760M Graphics",
|
|
wantExact: true,
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "exact match 15BF revision 00 returns 780M",
|
|
deviceID: "15bf",
|
|
revisionID: "00",
|
|
wantName: "AMD Radeon 780M Graphics",
|
|
wantExact: true,
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "device-only match returns first entry for device",
|
|
deviceID: "1506",
|
|
revisionID: "",
|
|
wantName: "AMD Radeon 610M",
|
|
wantExact: false,
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "unknown device not found",
|
|
deviceID: "dead",
|
|
revisionID: "00",
|
|
wantName: "",
|
|
wantExact: false,
|
|
wantFound: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
gotName, gotExact, gotFound := lookupAmdgpuNameInFile(tt.deviceID, tt.revisionID, idsPath)
|
|
assert.Equal(t, tt.wantName, gotName, "name")
|
|
assert.Equal(t, tt.wantExact, gotExact, "exact")
|
|
assert.Equal(t, tt.wantFound, gotFound, "found")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAmdGpuNameFromIdsFile(t *testing.T) {
|
|
// Test that getAmdGpuName resolves a name when we can't inject the ids path.
|
|
// We only verify behavior when product_name is missing and device/revision
|
|
// would be read from sysfs; the actual lookup uses /usr/share/libdrm/amdgpu.ids.
|
|
// So this test focuses on normalizeAmdgpuName and that lookupAmdgpuNameInFile
|
|
// returns the expected name for our test-data file.
|
|
idsPath := filepath.Join("test-data", "amdgpu.ids")
|
|
name, exact, found := lookupAmdgpuNameInFile("1435", "ae", idsPath)
|
|
require.True(t, found)
|
|
require.True(t, exact)
|
|
assert.Equal(t, "AMD Custom GPU 0932", name)
|
|
assert.Equal(t, "AMD Custom GPU 0932", normalizeAmdgpuName(name))
|
|
|
|
// " Graphics" suffix is trimmed by normalizeAmdgpuName
|
|
name2 := "AMD Radeon 860M Graphics"
|
|
assert.Equal(t, "AMD Radeon 860M", normalizeAmdgpuName(name2))
|
|
}
|