Files
ProxmoxDash/disks_test.go
2026-08-21 19:17:30 +03:00

142 lines
4.9 KiB
Go

package main
import (
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"time"
)
func TestApplySMARTJSONReadsNVMeSelfTestHistory(t *testing.T) {
disk := DiskMetrics{Path: "/dev/nvme0n1"}
output := []byte(`{
"model_name":"ADATA LEGEND 900",
"smart_status":{"passed":true},
"power_on_time":{"hours":960},
"nvme_self_test_log":{
"current_self_test_operation":{"value":0,"string":"No self-test in progress"},
"table":[{
"self_test_code":{"value":1,"string":"Short"},
"self_test_result":{"value":0,"string":"Completed without error"},
"power_on_hours":959
}]
}
}`)
before := time.Now().Add(-2 * time.Hour)
applySMARTJSON(&disk, output)
after := time.Now()
if disk.SelfTestNeverRun {
t.Fatal("завершённый NVMe self-test ошибочно отмечен как никогда не запускавшийся")
}
if disk.LastSelfTest != "Short · Completed without error" {
t.Fatalf("неожиданный результат self-test: %q", disk.LastSelfTest)
}
parsed, err := time.Parse(time.RFC3339, disk.LastSelfTestAt)
if err != nil || parsed.Before(before) || parsed.After(after) {
t.Fatalf("неверно вычислено время self-test: %q, err=%v", disk.LastSelfTestAt, err)
}
if len(disk.AttentionReasons) != 0 || disk.SMARTStatus != "Исправен" {
t.Fatalf("исправный NVMe требует внимания: %+v", disk)
}
}
func TestIsPhysicalDiskName(t *testing.T) {
tests := map[string]bool{
"sda": true, "sdb": true, "nvme0n1": true,
"loop0": false, "dm-0": false, "zram0": false, "md0": false,
}
for name, expected := range tests {
if actual := isPhysicalDiskName(name); actual != expected {
t.Fatalf("isPhysicalDiskName(%q) = %v, ожидалось %v", name, actual, expected)
}
}
}
func TestReadDiskIOStats(t *testing.T) {
path := filepath.Join(t.TempDir(), "diskstats")
data := " 8 0 sda 100 2 300 40 50 3 700 80 4 90 120 0 0 0 0 0 0\n 7 0 loop0 999 0 999 0 999 0 999 0 0 0 0\n"
if err := os.WriteFile(path, []byte(data), 0o600); err != nil {
t.Fatal(err)
}
stats := readDiskIOStats(path)
disk, ok := stats["sda"]
if !ok || disk.reads != 100 || disk.readSectors != 300 || disk.writes != 50 || disk.writeSectors != 700 || disk.inFlight != 4 || disk.ioMS != 90 || disk.weightedMS != 120 {
t.Fatalf("unexpected stats: %+v", disk)
}
if _, exists := stats["loop0"]; exists {
t.Fatal("virtual device must be ignored")
}
}
func TestSumFilesystemUsage(t *testing.T) {
device := lsblkDevice{Children: []lsblkDevice{
{FSUsed: json.RawMessage(`100`), FSAvail: json.RawMessage(`300`)},
{FSUsed: json.RawMessage(`200`), FSAvail: json.RawMessage(`400`)},
}}
used, total, ok := sumFilesystemUsage(device)
if !ok || used != 300 || total != 1000 {
t.Fatalf("неожиданное использование диска: used=%d total=%d ok=%v", used, total, ok)
}
}
func TestApplyATAWearAndReason(t *testing.T) {
var smart smartctlJSON
attribute := struct {
Name string `json:"name"`
Value int `json:"value"`
Threshold int `json:"thresh"`
WhenFailed string `json:"when_failed"`
Raw struct {
String string `json:"string"`
Value int64 `json:"value"`
} `json:"raw"`
}{Name: "SSD_Life_Left", Value: 92, WhenFailed: "FAILING_NOW"}
smart.ATAAttributes.Table = append(smart.ATAAttributes.Table, attribute)
disk := DiskMetrics{}
applyATAAttributes(&disk, smart)
if disk.WearUsedPercent == nil || *disk.WearUsedPercent != 8 {
t.Fatalf("неожиданный износ: %v", disk.WearUsedPercent)
}
if len(disk.AttentionReasons) != 1 {
t.Fatalf("причина предупреждения не добавлена: %+v", disk)
}
}
func TestHistoricalATAFailureDoesNotRequireAttention(t *testing.T) {
var smart smartctlJSON
attribute := struct {
Name string `json:"name"`
Value int `json:"value"`
Threshold int `json:"thresh"`
WhenFailed string `json:"when_failed"`
Raw struct {
String string `json:"string"`
Value int64 `json:"value"`
} `json:"raw"`
}{Name: "Airflow_Temperature_Cel", WhenFailed: "In_the_past"}
attribute.Raw.String = "46 (Min/Max 26/47 #15)"
smart.ATAAttributes.Table = append(smart.ATAAttributes.Table, attribute)
disk := DiskMetrics{}
applyATAAttributes(&disk, smart)
applySMARTExitStatus(&disk, 32|64|128)
if len(disk.AttentionReasons) != 0 {
t.Fatalf("историческая ошибка стала активной: %+v", disk.AttentionReasons)
}
if len(disk.SMARTHistory) != 4 {
t.Fatalf("история SMART потеряна: %+v", disk.SMARTHistory)
}
}
func TestSMARTErrorLogGrowthRequiresAttention(t *testing.T) {
old := DiskMetrics{SMARTErrorCount: 2}
current := DiskMetrics{SMARTErrorCount: 3}
changes := diskCounterChanges(old, current)
if len(changes) != 1 || !strings.Contains(changes[0], "2 → 3") {
t.Fatalf("рост журнала не обнаружен: %+v", changes)
}
}