356 lines
8.2 KiB
Go
356 lines
8.2 KiB
Go
// Copyright (c) 2024 Joshua Rich <joshua.rich@gmail.com>
|
|
//
|
|
// This software is released under the MIT License.
|
|
// https://opensource.org/licenses/MIT
|
|
|
|
//nolint:paralleltest
|
|
package cpu
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"math/rand"
|
|
"path/filepath"
|
|
"reflect"
|
|
"slices"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tklauser/go-sysconf"
|
|
|
|
"github.com/joshuar/go-hass-agent/internal/hass/sensor"
|
|
"github.com/joshuar/go-hass-agent/internal/hass/sensor/types"
|
|
"github.com/joshuar/go-hass-agent/internal/linux"
|
|
)
|
|
|
|
func Test_cpuUsageSensor_generateValues(t *testing.T) {
|
|
skipCI(t)
|
|
|
|
clktck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
|
require.NoError(t, err)
|
|
|
|
validValues := make([]string, 0, 10)
|
|
|
|
for range 10 {
|
|
validValues = append(validValues, strconv.Itoa(rand.Intn(999999))) //nolint:gosec
|
|
}
|
|
|
|
type fields struct {
|
|
cpuID string
|
|
usageAttributes map[string]any
|
|
Sensor linux.Sensor
|
|
}
|
|
type args struct {
|
|
details []string
|
|
clktk int64
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
fields fields
|
|
want int
|
|
}{
|
|
{
|
|
name: "valid values",
|
|
args: args{clktk: clktck, details: validValues},
|
|
fields: fields{cpuID: "cpu"},
|
|
want: len(validValues) + 1,
|
|
},
|
|
{
|
|
name: "invalid values",
|
|
args: args{clktk: clktck, details: make([]string, 0)},
|
|
fields: fields{cpuID: "cpu"},
|
|
want: 0,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
s := &cpuUsageSensor{
|
|
cpuID: tt.fields.cpuID,
|
|
usageAttributes: tt.fields.usageAttributes,
|
|
Sensor: tt.fields.Sensor,
|
|
}
|
|
s.generateValues(tt.args.clktk, tt.args.details)
|
|
assert.Len(t, s.usageAttributes, tt.want)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_cpuUsageSensor_Name(t *testing.T) {
|
|
type fields struct {
|
|
cpuID string
|
|
usageAttributes map[string]any
|
|
Sensor linux.Sensor
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
want string
|
|
fields fields
|
|
}{
|
|
{
|
|
name: "total",
|
|
fields: fields{cpuID: "cpu"},
|
|
want: "Total CPU Usage",
|
|
},
|
|
{
|
|
name: "core",
|
|
fields: fields{cpuID: "cpu2"},
|
|
want: "Core 2 CPU Usage",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
s := &cpuUsageSensor{
|
|
cpuID: tt.fields.cpuID,
|
|
usageAttributes: tt.fields.usageAttributes,
|
|
Sensor: tt.fields.Sensor,
|
|
}
|
|
if got := s.Name(); got != tt.want {
|
|
t.Errorf("cpuUsageSensor.Name() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_cpuUsageSensor_ID(t *testing.T) {
|
|
type fields struct {
|
|
cpuID string
|
|
usageAttributes map[string]any
|
|
Sensor linux.Sensor
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
want string
|
|
fields fields
|
|
}{
|
|
{
|
|
name: "total",
|
|
fields: fields{cpuID: "cpu"},
|
|
want: "total_cpu_usage",
|
|
},
|
|
{
|
|
name: "core",
|
|
fields: fields{cpuID: "cpu2"},
|
|
want: "core_2_cpu_usage",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
s := &cpuUsageSensor{
|
|
cpuID: tt.fields.cpuID,
|
|
usageAttributes: tt.fields.usageAttributes,
|
|
Sensor: tt.fields.Sensor,
|
|
}
|
|
if got := s.ID(); got != tt.want {
|
|
t.Errorf("cpuUsageSensor.ID() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_usageWorker_newUsageSensor(t *testing.T) {
|
|
skipCI(t)
|
|
|
|
clktck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
|
require.NoError(t, err)
|
|
|
|
validValues := []string{"cpu", "100", "0", "0", "0", "0", "0", "0", "0", "0"}
|
|
validSensor := &cpuUsageSensor{
|
|
cpuID: "cpu",
|
|
Sensor: linux.Sensor{
|
|
IconString: "mdi:chip",
|
|
UnitsString: "%",
|
|
DataSource: linux.DataSrcProcfs,
|
|
StateClassValue: types.StateClassMeasurement,
|
|
IsDiagnostic: false,
|
|
},
|
|
}
|
|
validSensor.generateValues(clktck, validValues[1:])
|
|
|
|
type fields struct {
|
|
clktck int64
|
|
}
|
|
type args struct {
|
|
details []string
|
|
diagnostic bool
|
|
}
|
|
tests := []struct {
|
|
want *cpuUsageSensor
|
|
name string
|
|
args args
|
|
fields fields
|
|
}{
|
|
{
|
|
name: "valid values",
|
|
args: args{details: validValues, diagnostic: false},
|
|
fields: fields{clktck: clktck},
|
|
want: validSensor,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := &usageWorker{
|
|
clktck: tt.fields.clktck,
|
|
}
|
|
if got := w.newUsageSensor(tt.args.details, tt.args.diagnostic); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("usageWorker.newUsageSensor() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_usageWorker_newCountSensor(t *testing.T) {
|
|
type fields struct {
|
|
logger *slog.Logger
|
|
clktck int64
|
|
}
|
|
type args struct {
|
|
icon string
|
|
details string
|
|
name string
|
|
}
|
|
tests := []struct {
|
|
fields fields
|
|
want *linux.Sensor
|
|
name string
|
|
args args
|
|
}{
|
|
{
|
|
name: "valid values",
|
|
args: args{name: "Total CPU Context Switches", icon: "mdi:counter", details: "400"},
|
|
fields: fields{logger: slog.Default()},
|
|
want: &linux.Sensor{
|
|
DisplayName: "Total CPU Context Switches",
|
|
UniqueID: "total_cpu_context_switches",
|
|
Value: 400,
|
|
IconString: "mdi:counter",
|
|
DataSource: linux.DataSrcProcfs,
|
|
StateClassValue: types.StateClassTotalIncreasing,
|
|
IsDiagnostic: true,
|
|
LastReset: "0001-01-01T00:00:00Z",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := &usageWorker{
|
|
clktck: tt.fields.clktck,
|
|
}
|
|
if got := w.newCountSensor(tt.args.name, tt.args.icon, tt.args.details); !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("usageWorker.newProcCntSensor() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_usageWorker_getStats(t *testing.T) {
|
|
skipCI(t)
|
|
|
|
clktck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
|
require.NoError(t, err)
|
|
|
|
type fields struct {
|
|
path string
|
|
clktck int64
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid",
|
|
fields: fields{clktck: clktck, path: filepath.Join(linux.ProcFSRoot, "stat")},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := &usageWorker{
|
|
clktck: tt.fields.clktck,
|
|
path: tt.fields.path,
|
|
}
|
|
got, err := w.getStats()
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("getStats() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
if !tt.wantErr {
|
|
// Require a total cpu usage sensor.
|
|
require.True(t, slices.ContainsFunc(got, func(d sensor.Details) bool {
|
|
return d.Name() == "Total CPU Usage"
|
|
}))
|
|
// Require at least 1 cpu core usage sensor.
|
|
require.True(t, slices.ContainsFunc(got, func(d sensor.Details) bool {
|
|
return d.Name() == "Core 1 CPU Usage"
|
|
}))
|
|
// Require a context switches sensor
|
|
require.True(t, slices.ContainsFunc(got, func(d sensor.Details) bool {
|
|
return d.Name() == "Total CPU Context Switches"
|
|
}))
|
|
// Require a processes total sensor
|
|
require.True(t, slices.ContainsFunc(got, func(d sensor.Details) bool {
|
|
return d.Name() == "Total Processes Created"
|
|
}))
|
|
// Require a procs running sensor
|
|
require.True(t, slices.ContainsFunc(got, func(d sensor.Details) bool {
|
|
return d.Name() == "Processes Running"
|
|
}))
|
|
// Require a processes blocked sensor
|
|
require.True(t, slices.ContainsFunc(got, func(d sensor.Details) bool {
|
|
return d.Name() == "Processes Blocked"
|
|
}))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Benchmark_getStats(b *testing.B) {
|
|
clktck, err := sysconf.Sysconf(sysconf.SC_CLK_TCK)
|
|
require.NoError(b, err)
|
|
|
|
w := &usageWorker{clktck: clktck}
|
|
|
|
b.Run(fmt.Sprintf("run %d", b.N), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
w.getStats() //nolint:errcheck
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewUsageWorker(t *testing.T) {
|
|
type args struct {
|
|
ctx context.Context
|
|
}
|
|
tests := []struct {
|
|
args args
|
|
want *linux.PollingSensorWorker
|
|
name string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid",
|
|
args: args{ctx: linux.NewContext(context.TODO())},
|
|
},
|
|
{
|
|
name: "invalid",
|
|
args: args{ctx: context.TODO()},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, err := NewUsageWorker(tt.args.ctx)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("NewUsageWorker() error = %v, wantErr %v", err, tt.wantErr)
|
|
return
|
|
}
|
|
// if !reflect.DeepEqual(got, tt.want) {
|
|
// t.Errorf("NewUsageWorker() = %v, want %v", got, tt.want)
|
|
// }
|
|
})
|
|
}
|
|
}
|